ツール呼び出しと function_tool
codex はモデルが呼べる全ツール(tools)を Responses API の function calling プロトコルに集約する。tools/ crate は純粋なプロトコル層を定義する——ToolSpec、ToolDefinition、ToolCall、ToolExecutor trait はここにあり、codex-core に依存しない。core/src/function_tool.rs は単なる re-export shim で、本当のツール登録、スケジューリング、安全チェックは core/src/tools/ と core/src/agent/ の下に分散している。この分割により、プロトコル層を app-server、extension、MCP adapter などが独立に再利用できる。
責務
- モデル可視のツール仕様の定義:
ToolSpec列挙型はFunction/Namespace/ToolSearch/WebSearch/Freeformの五つをカバーし、Responses API のtoolsフィールドに直接シリアライズされる (codex-rs/tools/src/tool_spec.rs:15-51)。 - ツールメタデータの記述:
ToolDefinitionは name、description、input_schema、output_schema、defer_loading を持ち、adapter がToolSpecに変換する (codex-rs/tools/src/tool_definition.rs:7-26)。 - 実行契約の統一:
ToolExecutor<Invocation>trait が「ツール名 -> spec -> handle」を一つのオブジェクトにまとめ、ToolExposureが direct / deferred / direct-model-only / hidden の四つの露出方法を制御する (codex-rs/tools/src/tool_executor.rs:14-69)。 - 呼び出しコンテキストの運搬:
ToolCallは turn_id、call_id、conversation_history、environments、payload を一つの値にまとめて実行器に渡す (codex-rs/tools/src/tool_call.rs:90-133)。 - sub-agent の登録:
AgentRegistryがマルチ agent ツリーを管理し、reserve_spawn_slotが CAS でmax_threads上限を守り、SpawnReservationが RAII で drop 時に解放する (codex-rs/core/src/agent/registry.rs:78-96)。
設計動機
なぜ core/src/function_tool.rs は一行の re-export だけなのか?このロジックは元々 core にあったが、後に引き抜かれて codex-tools crate になり、core は古い import が壊れないよう別名を残した。この分割の動機は:app-server、extension runtime、MCP adapter がどれも ToolSpec を組み立てて ToolCall を処理する必要があるが、codex-core の全依存を引き込みたくないからだ。プロトコル層を独立 crate にすれば、下流は codex-tools + codex-protocol だけに依存すればよい。
ToolExposure の四つの値は現実の矛盾を反映する:ツールリストが長くなるほど、モデルが間違ったツールを選ぶ確率が上がる。Deferred はツールを最初はモデルに晒さず、モデルが自発的に tool_search した時にだけ見つかるようにする——N 個のツールの初期 context を 1 個の tool_search 呼び出しに圧縮する。DirectModelOnly は code mode 専用:code mode はネストされたサブセッションで、メインセッションから見えるが code mode サブセッションからは見えないツールはこの路を使う。Hidden は内部スケジューリング用で、request_plugin_install のようなモデルに直接呼ばせたくないツールに使う。
AgentRegistry の核心は total_count: AtomicUsize だ。マルチ agent が並発 spawn する時、compare_exchange_weak で自旋して枠を奪い合い、取れなければ AgentLimitReached を返す。SpawnReservation は Arc<AgentRegistry> を持ち、drop 時に自動で fetch_sub(1) して枠を解放する——Rust の定石で、手動 release 忘れを防ぐ。
主要ファイル
codex-rs/core/src/function_tool.rs:1-1 — ファイル全体で pub use codex_tools::FunctionCallError の一行のみ。core の古い import 入口。codex-rs/tools/src/lib.rs:1-107 — codex-tools crate のルート。公開 re-export をすべて列挙。codex-rs/tools/src/tool_spec.rs:15-51 — ToolSpec 列挙型。五種のツール型の serde tag。codex-rs/tools/src/tool_definition.rs:7-26 — ToolDefinition。ツールのメタデータ、into_deferred 変換付き。codex-rs/tools/src/tool_executor.rs:14-69 — ToolExposure と ToolExecutor trait。ランタイム契約。codex-rs/tools/src/tool_call.rs:62-101 — ToolEnvironment と ToolCall。一回の呼び出しの完全なコンテキスト。codex-rs/core/src/agent/registry.rs:22-96 — AgentRegistry と reserve_spawn_slot。CAS による枠管理。codex-rs/core/src/agent/registry.rs:279-316 — SpawnReservation。RAII で spawn 枠を解放。codex-rs/core/src/agent/builtins/awaiter.toml:1-40 — 組み込み awaiter agent の TOML 設定。sub-agent の挙動ルールを定義。ToolSpec は Responses API が要求する JSON 形態に直接 serde され、#[serde(tag = "type")] で各バリアントがシリアライズ時に type フィールドを持つ。これが codex と OpenAI API の間の最小契約だ:
// tool_spec.rs:15-51 — 五种工具类型,直接序列化成 API JSON
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum ToolSpec {
#[serde(rename = "function")]
Function(ResponsesApiTool),
#[serde(rename = "namespace")]
Namespace(ResponsesApiNamespace),
#[serde(rename = "tool_search")]
ToolSearch { execution: String, description: String, parameters: JsonSchema },
#[serde(rename = "web_search")]
WebSearch { /* ... 外部搜索开关 */ },
#[serde(rename = "custom")]
Freeform(FreeformTool),
}ToolExecutor trait が「ツール仕様」と「実行ロジック」を一つに結ぶ。handle は boxed future を返し、シグネチャは統一されている。具体的な実装は shell コマンド、apply_patch、MCP call、extension callback のいずれでもよい。exposure() はデフォルトで Direct で、deferred ツールは override が必要:
// tool_executor.rs:49-69 — 所有工具运行时的统一接口
pub trait ToolExecutor<Invocation>: Send + Sync {
fn tool_name(&self) -> ToolName;
fn spec(&self) -> ToolSpec;
fn exposure(&self) -> ToolExposure { ToolExposure::Direct }
fn search_info(&self) -> Option<ToolSearchInfo> { /* 从 spec 派生 */ }
fn supports_parallel_tool_calls(&self) -> bool { false }
fn handle(&self, invocation: Invocation) -> ToolExecutorFuture<'_>;
}AgentRegistry の CAS 自旋による枠奪取は典型パターン:Ordering::AcqRel で compare_exchange し、失敗したら最新値を読み直して loop する。Mutex より軽く、高頻度 spawn に向く:
// agent/registry.rs:260-276 — CAS 抢占 spawn 槽位
fn try_increment_spawned(&self, max_threads: usize) -> bool {
let mut current = self.total_count.load(Ordering::Acquire);
loop {
if current >= max_threads {
return false;
}
match self.total_count.compare_exchange_weak(
current, current + 1, Ordering::AcqRel, Ordering::Acquire,
) {
Ok(_) => return true,
Err(updated) => current = updated,
}
}
}データフロー
境界と失敗
function_argumentsの型不一致:ToolCall::function_argumentsはpayloadがToolPayload::Functionであることを検査し、違えばFunctionCallError::Fatalを返し、「tool invoked with incompatible payload」を伝える (codex-rs/tools/src/tool_call.rs:123-133)。- nickname プール枯渇:
reserve_agent_nicknameはプールが尽きたらused_agent_nicknamesを空にしてnickname_reset_count += 1し、以後の nickname に "the 2nd" / "the 3rd" のサフィックスを付ける。プールが完全に空になったらUnsupportedOperationを返す (codex-rs/core/src/agent/registry.rs:187-225)。 - agent path 衝突:
reserve_agent_pathはすでに存在する path に遭遇するとUnsupportedOperationを返す。呼び出し側は spawn 前にチェックする必要がある (codex-rs/core/src/agent/registry.rs:227-244)。 max_threads上限:reserve_spawn_slotが枠を取れない時はAgentLimitReached { max_threads }を返す。extension は無限リトライせずこのエラーを処理しなければならない (codex-rs/core/src/agent/registry.rs:82-96)。DirectModelOnlyと code mode:is_directはDirectとDirectModelOnlyをどちらも direct とみなすが、code mode のネストサブセッションでは前者だけがネストツールとして許され、これが spec がcollect_code_mode_tool_definitionsに入るかを決める (codex-rs/tools/src/tool_executor.rs:38-42)。
まとめ
ツール周りはプロトコル層(codex-tools)とランタイム層(core/src/tools/、agent/)がきれいに分かれている:プロトコル層は「どうモデルと話すか」だけを管し、ランタイム層は「どう実行するか」を管する。マルチ agent の並発管理は AgentRegistry が CAS + RAII の小さなパターンに収束し、extension が新 thread を開くには必ずこの路を通る。具体的なツール実装(apply_patch など)を見るには apply_patch プロトコル へ、ツール呼び出しがどうメインループに詰め込まれるかは Agent メインループ へ。