Skip to content

メッセージ型と convertToLlm

源码版本v0.73.1

messages.tspi-agent-coreAgentMessage に四つのカスタムメッセージ型を追加する:bashExecutioncustombranchSummarycompactionSummary。基底の Agentuser/assistant/toolResult しか認識せず、convertToLlm がこの四つのカスタム型を Message[] に翻訳し、excludeFromContext が立った bash 実行結果をフィルタする。宣言マージ (declaration merging) で 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 穷尽チェック (exhaustive check) を使い、新しい型を追加して処理を忘れるとコンパイルエラーになる:

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 装配 の境界セクション参照。
  • 圧縮も同一関数を使用:AgenttransformToLlm と compaction の generateSummary はどちらも convertToLlm を使い、圧縮時に見えるコンテキストと provider に送る時のコンテキストが一致するようにする。

まとめ

messages.ts は TypeScript の宣言マージで AgentMessage に四つのカスタム型を追加し、convertToLlm が provider 送信前にそれらを user メッセージにフラット化する。UI レイヤは差分レンダリング用に元の型を残す。装配時にはこの関数は createAgentSession で blockImages フィルタのラッパーを一层被せられる。createAgentSession 装配 参照。圧縮フローが CompactionSummaryMessage を生成する詳細は compaction ディレクトリにて。