訊息類型與 convertToLlm
messages.ts 給 pi-agent-core 的 AgentMessage 加四種自訂訊息類型:bashExecution、custom、branchSummary、compactionSummary。底層 Agent 只認 user/assistant/toolResult,convertToLlm 把這四種自訂類型翻譯成 Message[],過濾掉 excludeFromContext 的 bash 執行結果。宣告合併透過 CustomAgentMessages 介面擴充,類型層和執行時層都對齊。
職責
- 類型擴充:用
declare module給CustomAgentMessages介面加四個 role,見packages/coding-agent/src/core/messages.ts:69-77。 - bash 執行訊息:
!命令的執行結果,帶command、output、exitCode、cancelled、truncated、fullOutputPath,可選excludeFromContext(對應!!前綴)。見packages/coding-agent/src/core/messages.ts:29-40。 - custom 訊息:擴充透過
sendMessage注入的訊息,customType區分類型,display控制是否在 UI 顯示,details是可選結構化資料。見packages/coding-agent/src/core/messages.ts:46-53。 - branch / compaction summary:fork 回主線時插入分支摘要,壓縮後插入壓縮摘要,都用
<summary>標籤包裹,見packages/coding-agent/src/core/messages.ts:55-67。 - 工廠函式:
createBranchSummaryMessage/createCompactionSummaryMessage/createCustomMessage把字串 timestamp 轉成數字毫秒。見packages/coding-agent/src/core/messages.ts:100-138。 - 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 上下文移除——這是使用者主動選擇「這步執行不讓模型看到」,常見於敏感操作或噪音輸出。
branchSummary 和 compactionSummary 用 <summary> XML 標籤包裹,這是 Anthropic 推薦的格式,模型會更傾向把標籤內容當成完整單元對待,而不是分散成段落。
關鍵檔案
packages/coding-agent/src/core/messages.ts:11-24—COMPACTION_SUMMARY_PREFIX/SUFFIX和BRANCH_SUMMARY_PREFIX/SUFFIX常數。packages/coding-agent/src/core/messages.ts:29-40—BashExecutionMessage介面。packages/coding-agent/src/core/messages.ts:46-53—CustomMessage<T>介面。packages/coding-agent/src/core/messages.ts:55-67—BranchSummaryMessage與CompactionSummaryMessage,後者還存tokensBefore用於診斷。packages/coding-agent/src/core/messages.ts:69-77—declare module宣告合併,擴充CustomAgentMessages。packages/coding-agent/src/core/messages.ts:82-98—bashExecutionToText,格式化 bash 輸出為 LLM 友善文字。packages/coding-agent/src/core/messages.ts:100-138— 三個 create 工廠函式。packages/coding-agent/src/core/messages.ts:148-195—convertToLlm,switch + filter 模式。
convertToLlm 用 switch + never 窮盡檢查,新增類型忘記處理會編譯報錯:
// 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 程式碼區塊,帶上退出碼和截斷提示:
// 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 包裝層:
createAgentSession在convertToLlm外再包一層convertToLlmWithBlockImages,動態讀 settings 替換圖片為佔位文字,見 createAgentSession 組裝 的邊界段。 - compaction 用同一函式:
Agent的transformToLlm和 compaction 的generateSummary都用convertToLlm,保證壓縮時看到的上下文和送 provider 時一致。
小結
messages.ts 用 TypeScript 宣告合併給 AgentMessage 加四種自訂類型,convertToLlm 在送 provider 前把它們拍平成 user 訊息,UI 層保留原類型用於差異化渲染。組裝時這個函式被 createAgentSession 包一層 blockImages 過濾,看 createAgentSession 組裝;壓縮流程產生 CompactionSummaryMessage 的細節在 compaction 目錄。