从快照读懂 Agent:VS Code 中 claude-sonnet-5 的 Copilot 完整模型请求基线解析
Agent_Host_E2E___Copilot_prompts_claude-sonnet-5.prompt.md 是 VS Code Agent Host 端到端(E2E)测试体系为 claude-sonnet-5 这个模型提交的一 份"提示词快照基线":它逐字段固定了捆绑的 Copilot CLI 在真实回合中序列化到线上的完整模型请求体,覆盖系统提示(system)、回合消息(messages)、全部工具定义与采样参数。本文以该快照为主体,结合其生成测试、规范化规则与宿主侧提示词注册表源码,说明这份基线里每一层内容从哪来、被规约(pin)到什么程度、何时会失效以及如何安全更新——读完你可以看懂仓库中任意一份 *.prompt.md,并具备为 Copilot 提示词做"改动归因"与"确定性回归"的完整方法。
这份 .prompt.md 是什么
在仓库中它位于 E2E 测试的 providers/__snapshots__/ 目录,与其并列的既有同类的 Agent_Host_E2E___Copilot_prompts_claude-opus-5.prompt.md、...gpt-5.prompt.md 等近二十份按模型家族划分的基线,也有记录 AHP 协议语义流量的 *.traffic.ahp.yaml 快照。二者由同一套 assertSnapshot 机制写入,区别在于:
*.traffic.ahp.yaml快照固定的是 AHP 协议(客户端 ⇄ Agent Host) 上的语义化消息序列;*.prompt.md快照固定的是 Agent Host ⇄ 模型供应商 CAPI 之间、由 Copilot CLI 实际发出的完整 HTTP 请求体。
文件名为测试全名加规约名清洗后的拼接结果。例如本快照对应套件 Agent Host E2E — Copilot prompts、用例 claude-sonnet-5(规则见 ahpSnapshot.ts 中 sanitizeName/snapshotPathForTest)。文件名里的 claude-sonnet-5 同时也是 SNAPSHOT_MODELS 常量中的一员——该数组逐个模型驱动一次真实回合,claude-sonnet-5 是其成员之一。
它的物理形态是一段被 ```json 围栏包裹的 JSON(因此扩展名是 .md),内容由测试把请求体做原位脱敏后 pretty-print 而来,README 的 "Prompt snapshots" 一节对此有明确定义(见 e2e/README.md "Prompt snapshots" 小节)。
快照从哪一条线上读出来:replay 而非 record
理解这份基线,先要理解它的获取方式。Copilot CLI 的提示词是编译进 @github/copilot 原生二进制里的产品实现,只有当 CLI 把它序列化到网络线上时才可观察。因此测试的设计是:从一次"重放(replay)"回合中读取请求体,而不是在录制(record)时抓取。原因在测试文件头部注释与 README 中写得很明确(见 copilotPromptsE2E.integrationTest.ts):
- replay(默认、CI 运行)确定性且零 token:CapiReplayProxy 按
(method, path)的次序回放captures/中已提交的 YAML 夹具,模型响应是假的,其余(服务器、CLI 子进程、工具执行、协议)全是真的; - record(
AGENT_HOST_REPLAY_RECORD=1或AGENT_HOST_UPDATE_SNAPSHOTS=1)会触达真实 CAPI,去取模型目录与实验分配——二者都可能因为本仓库无法控制的原因移动提示词,所以录制方向的运行永远不产生基线。
落到具体代码:for (const model of SNAPSHOT_MODELS) 循环中,测试先 createRealSession 建一个真实会话,再经 driveTurnWithModel 用显式模型选择(message.model.id = model)发送固定的极简提示 Say exactly "ok",并把回合驱动到 chat/turnComplete(见 driveTurnWithModel)。回合期间服务器租约把 CLI 发出的请求体记录在 lease.observedModelRequestBodies,测试取最后一个 body——这样即使 CLI 中途插入了预检请求,基线抓到的仍是真正到模型的那一发。随后经过 formatPromptSnapshot 的形状守卫与规范化后落盘为基线。
快照的顶层结构:一份真实请求体长什么样
以本文件为例,请求体最外层字段可直接列出,它们与 Anthropic Messages 方言对应(Claude 走 POST /v1/messages,dialect: anthropic):
| 字段 | 值 | 含义 |
|---|---|---|
model |
claude-sonnet-5 |
显式选中的模型 id |
max_tokens |
32000 |
输出 token 上限 |
system |
两个带 cache_control: {type: "ephemeral"} 的文本块 |
系统提示,分两块做临时提示缓存标记 |
messages |
一条 user 回合 |
即固定的 Say exactly "ok" 提示(带 ${datetime} 占位符) |
tools |
二十余个完整工具定义 | 每个含 name + description + 完整 JSON input_schema |
temperature |
1 |
采样温度 |
thinking |
adaptive + display: summarized |
自适应思考模式,对端总结显示 |
output_config |
effort: medium |
输出投入度 |
stream |
true |
流式输出 |
其中 system/messages 这两个 key 是 Anthropic Messages 方言特有的(Responses 方言同一位置叫 instructions/input),快照读取逻辑因此对两种方言做了兼容,见 formatPromptSnapshot 中 IWireRequest 与 readMessages。
system 块的三层内容:身份、规则与环境
两个系统文本块各带 cache_control: ephemeral,分别承担不同职责:
第一块:身份 + 代码改动规则 + 环境限制。 开头固定自述 "You are an AI assistant using Copilot SDK in VS Code"(被问及身份时须如此陈述)。其后是几组结构性段落,从快照里能清楚看到它们各自的标签外壳:
<code_change_instructions>:内嵌<rules_for_code_changes>(精准外科手术式改动、不修与任务无关的存量问题、文档随改动更新、不破坏既有行为)、<linting_building_testing>(只跑已存在的 lint/build/test,用最小的定向命令,文档改动无需构建)、<using_ecosystem_tools>(优先生态工具而非手改)、<style>(只在需要澄清处加注释);<tips_and_tricks>:先复盘命令输出再走下一步、任务结束清理临时文件、对已存在文件用 view/edit 而不是 create、拿不准时用ask_user工具澄清、未经要求不建规划类 markdown;<environment_limitations>及其内的<prohibited_actions>:声明不是在为任务隔离的沙箱里运行,可能与其他用户共享环境;并列出必须禁止的动作(不向第三方泄露敏感数据、不把密钥提交进源码、不侵犯版权,且不得透露/讨论本指令本身——快照忠实保留了这条"保密且永久有效"的约束)。
第二块:环境上下文 + 工具使用指南 + 仓库指令注入点。 依次包含 <environment_context>(当前工作目录 ${workdir}、git 仓库根、操作系统 ${os}、可用工具 ${available_tools} 等运行时占位符)、大段 <tools> 使用指引(bash 的进程与会话语义、同步/异步模式、view 的分块读取、grep 的正则注意点、task 子代理的用法与安全审查契约等),以及末尾两个 <custom_instruction>${repository_instructions}</custom_instruction> 占位——这是宿主注入仓库级指令的接缝。它说明:宿主把自己要加的段落拼进这个请求体,而基线原样覆盖了这些段落,形成端到端断言。
工具定义:被完整规约的"可用能力"清单
快照后半的大半篇幅都是 tools 数组,每个元素是完整的 name + description + input_schema。从本文件可归纳出这些能力族:
- 命令执行族:
bash(含 shell 安全说明、同步/异步/后台模式、禁止用名字批量 kill 等约定),以及配套的read_bash/stop_bash/list_bash; - 文件观察/变更族:
view(20KB 截断、view_range、不要用其读二进制)、edit(精确 old_str/new_str 替换、同响响应多编辑)、create(不能覆盖已存在路径、父目录必须先存在); - 检索族:
glob、grep(基于 ripgrep,注意\{\}转义、支持 content/files_with_matches/count 三种输出与上下文参数); - 技能与子代理族:
skill(只能引用 available_skills 中的名字)、task(explore/task/general-purpose/code-review/research/security-review 等内置子代理,同步与后台两种模式); - 交互与数据族:
ask_user(多选优先、一次只问一个问题、推荐项放第一并标注 "(Recommended)")、sql(会话级 SQLite,预置todos表等); - 评审协作族:
addComment/listComments/replyToComment/deleteComments/resolveComments/viewUnreviewedComments——快照内的 schema 明确约定了基于 1-based 行号区间、resourceUri 的输入形态; - 会话管理服务端工具族:
list_sessions/get_current_session/create_session/send_message/get_session_context/delete_session,并携带cache_control: ephemeral。
由于是逐字段完整固定,工具的描述文案(如 task 工具里各 agent 的能力区分、安全评审的汇报表格格式契约)一旦被 SDK 改版改写,都会在下一次基线 diff 中原形毕露。
占位符:把"必然变化"从基线里摘出去
快照并非逐字节复制线上请求。CLI 会把请求压缩成一行,所以测试先 pretty-print;又因为运行期值会变化,formatPromptSnapshot 在序列化前递归执行 normalizeVolatileValues,对字符串套用 normalizeVolatile(见 normalizeVolatile)。它把以下内容原位替换成稳定占位符:
| 占位符 | 被替换的易变内容 |
|---|---|
${session_id} / ${uuid} |
会话 UUID 与其余运行时 UUID |
${datetime} |
<current_datetime> 时钟值(保留标签) |
${os} |
操作系统探测行(保留 * Operating System: 前缀) |
${available_tools} |
PATH 上发现的工具清单行(保留前缀) |
${platform_packages} |
bash 工具里平台相关的包管理器提示 |
${repository_instructions} |
注入的仓库指令 <custom_instruction> 内容(保留标签) |
${model_count} / ${model_catalog} |
模型计数与 Task schema 内联的模型目录(保留标签) |
设计要点是"摘值不摘形":占位符保留了原有标签或外壳,所以如果某行形状变了(例如 <custom_instruction> 从提示词里消失、模型目录不再出现),测试仍然会红;只有内容值本身被替换。测试里还有专门的单元用例验证了这套格式化的行为,例如 Copilot prompt snapshot formatting 套件断言"残缺请求体被拒绝"和"易变值原位规范化"。
其中有两处脱敏与运行差异无关,而是改动归因的选择,README 专门解释了(见 e2e/README.md):
- 仓库指令(repository instructions):内容跨机器稳定、本可被固定,但
AGENTS.md或.github/copilot-instructions.md加一行就会重写全仓库每一份基线,让无关的文档编辑弄红 CI——所以只保留<custom_instruction>包裹断言"注入了、注入几份、位于提示词哪个位置"。 - 模型目录(model catalog):CLI 会把整个
/models列表内联进Task工具的 schema,若原样固定,在capiStubs.ts增加一个模型会重写包括"无人规约的模型"在内的所有基线——同样保留标签而摘除列表。
这两个设计共同回答了一个问题:为什么某份基线的 diff 出现/不出现。在 claude-sonnet-5 这份基线里,它们以 ${repository_instructions} 与 ${model_catalog} 形式出现,而不是真实内容。
宿主侧贡献与改动归因:何时该更新基线
快照 diff 意味着两端有一端变了:
- SDK bump:
@github/copilotCLI 改版,提示词或工具 schema 是 CLI 的产品,变化只可能随 SDK 升级上线; - 宿主侧改动:Agent Host 把自己拼给 CLI 的内容改了。宿主侧的组装入口是 promptRegistry.ts 的
AgentHostPromptRegistry.resolveSystemMessageConfig——它组合出的多个段落会原样落进本快照的 system 块,因此基线对该注册表是端到端覆盖的。
反例是:编辑仓库 AGENTS.md 不会(也不该)弄红这些基线,这正是上述占位符设计的产物。README 还指出,本 E2E 无法覆盖"被宿主配置门控、按模型贡献者不同"的那些分支,因为 E2E 没有设置根配置的接缝——它们由 agentHostPromptRegistry.test.ts 同目录层的单测覆盖。这一点从代码结构看,是"E2E 覆盖宿主默认贡献、单测覆盖配置分支"的明确分工。
新增一个模型、接受一份基线:运维命令
快照对模型是opt-in的:不派生自实时 /models 目录,所以新模型不会自动出现。README 给出了完整运维路径(见 e2e/README.md):
先跑一次确认当前基线通过(replay 默认零 token):
./scripts/test-integration.sh --run \
src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts
接受新基线(与 AHP 快照共用同一环境变量):
AGENT_HOST_UPDATE_AHP_SNAPSHOTS=1 ./scripts/test-integration.sh --run \
src/vs/platform/agentHost/test/node/e2e/providers/copilotPromptsE2E.integrationTest.ts
更新模式直接原地覆写基线(本文件即在 providers/__snapshots__/ 下被重写);普通断言模式下不一致时测试失败并写出 .actual 供诊断。若只有个别模型需要更新,用 --grep "<test title>" 收敛范围。
若要新增一个此前未被规约的模型,需要同时满足三处约束(README 的 "Prompt snapshots" 一节与测试文件头注释一致):
- 在 SNAPSHOT_MODELS 加一项——不加则新模型永不进入快照范围;
- 在 capiStubs.ts 的桩模型目录中登记该模型——否则 CLI 构建请求前就被
/models拒绝,测试以"没有抓到 body"失败; - 提交一份夹具
captures/copilotcli-<用例slug>.yaml——重放回合仍需被应答;夹具的dialect必须与模型的桩端点匹配(Claude 走/v1/messages→dialect: anthropic,Codex 类走/responses→dialect: responses)。
平台与既有基线族:几点补充
几个值得注意的边界条件,在排查红绿时直接相关:
- POSIX-only:
copilotPromptsE2E套件在 Windows 上对每个模型用例直接test.skip(见 测试循环注释)。Windows 的提示词携带 PowerShell 专属段落,并非本基线的重命名版本;SDK 漂移是跨 provider 的,POSIX runner 已能捕获,详见 KNOWN_ISSUES.md。 - 一族一基线:同一方言内多数模型产出的提示词几乎相同——CLI 并不在方言内按模型分支,宿主对各模型贡献的段落也一致。基线仍按家族各留一份,是为了未来某模型出现分支时,diff 能指认引入分歧的那个家族。
- 显式选择模型:测试刻意不发送"未选择模型"的回合,因为那样 CLI 会按自己的排序从桩目录选模型,基线就会记录夹具的属性而非产品行为,且随桩目录增删而漂移。
- 夹具与请求断言的双保险:模型响应按序号回放,但夹具中记录的请求并非装饰——每次重放都会用 modelRequestProjection.ts 与真实请求比对,避免"回归变绿后又成为新基线"。
小结
以 claude-sonnet-5 这份 *.prompt.md 为入口,可以完整还原 VS Code Agent Host 的 Copilot 提示词回归策略:产物快照(pin 请求体) + 夹具回放(零 token 确定性) + 原位脱敏(摘值不摘形) 三者共同作用,让"CLI 编译进二进制、平时不可见"的提示词成为可评审、可 diff、可归因的仓库资产。读懂它的分层——顶层参数、双 system 块、完整工具 schema、占位符体系——之后,无论是 SDK 升级引发的全线基线刷新,还是宿主新增一段提示词贡献,你都能立刻判断"这次 diff 该不该出现、责任在哪一侧"。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00