Skip to content

消息类型与 convertToLlm

源码版本v0.73.1

messages.tspi-agent-coreAgentMessage 加四种自定义消息类型:bashExecutioncustombranchSummarycompactionSummary。底层 Agent 只认 user/assistant/toolResult,convertToLlm 把这四种自定义类型翻译成 Message[],过滤掉 excludeFromContext 的 bash 执行结果。声明合并通过 CustomAgentMessages 接口扩展,类型层和运行时层都对齐。

职责

  1. 类型扩展:用 declare moduleCustomAgentMessages 接口加四个 role,见 packages/coding-agent/src/core/messages.ts:69-77
  2. bash 执行消息:! 命令的执行结果,带 commandoutputexitCodecancelledtruncatedfullOutputPath,可选 excludeFromContext(对应 !! 前缀)。见 packages/coding-agent/src/core/messages.ts:29-40
  3. custom 消息:扩展通过 sendMessage 注入的消息,customType 区分类型,display 控制是否在 UI 显示,details 是可选结构化数据。见 packages/coding-agent/src/core/messages.ts:46-53
  4. branch / compaction summary:fork 回主线时插入分支摘要,压缩后插入压缩摘要,都用 <summary> 标签包裹,见 packages/coding-agent/src/core/messages.ts:55-67
  5. 工厂函数:createBranchSummaryMessage / createCompactionSummaryMessage / createCustomMessage 把字符串 timestamp 转成数字毫秒。见 packages/coding-agent/src/core/messages.ts:100-138
  6. convertToLlm:把自定义消息转成 Message[],bash 用 bashExecutionToText 转成 user 消息,branch/compaction summary 包 <summary> 标签,excludeFromContext 的 bash 直接返回 undefined 过滤掉。见 packages/coding-agent/src/core/messages.ts:148-195

设计动机

为什么不直接复用 user 消息?因为 UI 层需要区分「这是 bash 执行输出」和「这是用户输入」,渲染样式、可折叠、可重发都不同。但 LLM 只需要看到文本——convertToLlm 在送 provider 前把所有自定义类型拍平成 user 消息,模型完全不知道有 custom 类型存在。这样既保留了 UI 的语义丰富性,又不污染模型上下文。

bashExecutionToText 把 bash 输出格式化成 Markdown 代码块,加上 Ran \command` 前缀和退出码后缀,让 LLM 看到的格式和用户在 UI 看到的差不多。!!前缀的 bash 通过excludeFromContext: true` 完全从 LLM 上下文移除——这是用户主动选择「这步执行不让模型看到」,常见于敏感操作或噪音输出。

branchSummarycompactionSummary<summary> XML 标签包裹,这是 Anthropic 推荐的格式,模型会更倾向于把标签内容当成完整单元对待,而不是分散成段落。

关键文件

convertToLlmswitch + never 穷尽检查,新增类型忘记处理会编译报错:

typescript
// packages/coding-agent/src/core/messages.ts:148-195
export function convertToLlm(messages: AgentMessage[]): Message[] {
	return messages
		.map((m): Message | undefined => {
			switch (m.role) {
				case "bashExecution":
					if (m.excludeFromContext) {
						return undefined;
					}
					return {
						role: "user",
						content: [{ type: "text", text: bashExecutionToText(m) }],
						timestamp: m.timestamp,
					};
				case "custom": {
					const content = typeof m.content === "string" ? [{ type: "text" as const, text: m.content }] : m.content;
					return { role: "user", content, timestamp: m.timestamp };
				}
				// ... branchSummary / compactionSummary ...
				case "user":
				case "assistant":
				case "toolResult":
					return m;
				default:
					const _exhaustiveCheck: never = m;
					return undefined;
			}
		})
		.filter((m) => m !== undefined);
}

bashExecutionToText 把执行结果包成 Markdown 代码块,带上退出码和截断提示:

typescript
// packages/coding-agent/src/core/messages.ts:82-98
export function bashExecutionToText(msg: BashExecutionMessage): string {
	let text = `Ran \`${msg.command}\`\n`;
	if (msg.output) {
		text += `\`\`\`\n${msg.output}\n\`\`\``;
	} else {
		text += "(no output)";
	}
	if (msg.cancelled) {
		text += "\n\n(command cancelled)";
	} else if (msg.exitCode !== null && msg.exitCode !== undefined && msg.exitCode !== 0) {
		text += `\n\nCommand exited with code ${msg.exitCode}`;
	}
	if (msg.truncated && msg.fullOutputPath) {
		text += `\n\n[Output truncated. Full output: ${msg.fullOutputPath}]`;
	}
	return text;
}

数据流

消息从产生到 LLM 的路径:

边界与失败

  • excludeFromContext:!! 前缀的 bash 执行结果在 convertToLlm 里被完全过滤,模型看不到,但 UI 仍展示,见 packages/coding-agent/src/core/messages.ts:152-156
  • custom content 双形态:CustomMessage.content 可以是 string 或 Array<TextContent | ImageContent>,string 时转成单元素 TextContent 数组,见 packages/coding-agent/src/core/messages.ts:162-168
  • timestamp 字符串转数字:三个 create 函数都用 new Date(timestamp).getTime(),传入已是数字 timestamp 会得到 NaN,调用方需保证传 ISO 字符串,见 packages/coding-agent/src/core/messages.ts:105-106
  • blockImages 包装层:createAgentSessionconvertToLlm 外再包一层 convertToLlmWithBlockImages,动态读 settings 替换图片为占位文本,见 createAgentSession 装配 的边界段。
  • compaction 用同一函数:AgenttransformToLlm 和 compaction 的 generateSummary 都用 convertToLlm,保证压缩时看到的上下文和送 provider 时一致。

小结

messages.ts 用 TypeScript 声明合并给 AgentMessage 加四种自定义类型,convertToLlm 在送 provider 前把它们拍平成 user 消息,UI 层保留原类型用于差异化渲染。装配时这个函数被 createAgentSession 包一层 blockImages 过滤,看 createAgentSession 装配;压缩流程产生 CompactionSummaryMessage 的细节在 compaction 目录。