メッセージ型と convertToLlm
messages.ts は pi-agent-core の AgentMessage に四つのカスタムメッセージ型を追加する:bashExecution、custom、branchSummary、compactionSummary。基底の Agent は user/assistant/toolResult しか認識せず、convertToLlm がこの四つのカスタム型を Message[] に翻訳し、excludeFromContext が立った bash 実行結果をフィルタする。宣言マージ (declaration merging) で 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 穷尽チェック (exhaustive check) を使い、新しい型を追加して処理を忘れるとコンパイルエラーになる:
// 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 装配 の境界セクション参照。 - 圧縮も同一関数を使用:
AgentのtransformToLlmと compaction のgenerateSummaryはどちらもconvertToLlmを使い、圧縮時に見えるコンテキストと provider に送る時のコンテキストが一致するようにする。
まとめ
messages.ts は TypeScript の宣言マージで AgentMessage に四つのカスタム型を追加し、convertToLlm が provider 送信前にそれらを user メッセージにフラット化する。UI レイヤは差分レンダリング用に元の型を残す。装配時にはこの関数は createAgentSession で blockImages フィルタのラッパーを一层被せられる。createAgentSession 装配 参照。圧縮フローが CompactionSummaryMessage を生成する詳細は compaction ディレクトリにて。