Werkzeugaufrufe und function_tool
codex bündelt alle vom Modell aufrufbaren Werkzeuge (tools) auf dem Function-Calling-Protokoll der Responses API. Der Crate tools/ definiert eine reine Protokollschicht — ToolSpec, ToolDefinition, ToolCall, der ToolExecutor-Trait liegen hier und hängen nicht von codex-core ab. core/src/function_tool.rs ist nur ein Re-Export-Shim; die eigentliche Werkzeugregistrierung, Planung und Sicherheitsprüfung verteilen sich auf core/src/tools/ und core/src/agent/. Diese Trennung erlaubt es, die Protokollschicht von app-server, Extension und MCP-Adapter unabhängig wiederzuverwenden.
Verantwortlichkeiten
- Modell-sichtbare Werkzeug-Spezifikation: Die
ToolSpec-Enum deckt fünf Typen ab —Function/Namespace/ToolSearch/WebSearch/Freeform— und serialisiert direkt in dastools-Feld der Responses API (codex-rs/tools/src/tool_spec.rs:15-51). - Werkzeug-Metadaten beschreiben:
ToolDefinitionträgt name, description, input_schema, output_schema, defer_loading; der Adapter wandelt es inToolSpecum (codex-rs/tools/src/tool_definition.rs:7-26). - Einheitlicher Ausführungsvertrag: Das
ToolExecutor<Invocation>-Trait kapselt „Werkzeugname -> spec -> handle" in einem Objekt;ToolExposuresteuert vier Expositionsarten — direct / deferred / direct-model-only / hidden (codex-rs/tools/src/tool_executor.rs:14-69). - Aufruf-Kontext mitführen:
ToolCallverpackt turn_id, call_id, conversation_history, environments und payload in einem Wert für den Executor (codex-rs/tools/src/tool_call.rs:90-133). - Sub-Agent registrieren:
AgentRegistryverwaltet den Multi-Agent-Baum;reserve_spawn_slotsichert dasmax_threads-Limit per CAS ab;SpawnReservationgibt den Slot über RAII im drop frei (codex-rs/core/src/agent/registry.rs:78-96).
Entwurfsbeweggründe
Warum besteht core/src/function_tool.rs nur aus einer einzigen Re-Export-Zeile? Früh steckte diese Logik in core, wurde dann in den codex-tools-Crate ausgekoppelt; in core bleibt nur ein Alias, damit alte Imports nicht brechen. Motiv der Trennung: app-server, Extension-Runtime und MCP-Adapter müssen alle ToolSpec konstruieren und ToolCall verarbeiten, ohne die gesamten Abhängigkeiten von codex-core hineinzuziehen. Ist die Protokollschicht ein eigener Crate, genügen downstream codex-tools + codex-protocol.
Die vier Werte von ToolExposure spiegeln einen realen Widerspruch: Je länger die Werkzeugliste, desto höher die Wahrscheinlichkeit, dass das Modell das falsche Werkzeug wählt. Deferred blendet ein Werkzeug vor dem Modell aus, bis es aktiv per tool_search gefunden wird — das presst den anfänglichen Kontext von N Werkzeugen auf einen tool_search-Aufruf zusammen. DirectModelOnly ist dem Code-Modus vorbehalten: Der Code-Modus ist eine verschachtelte Sub-Sitzung, und Werkzeuge, die in der Hauptsitzung sichtbar, aber in der Code-Modus-Sub-Sitzung unsichtbar sind, gehen diesen Weg. Hidden dient der internen Steuerung, etwa für Werkzeuge wie request_plugin_install, die das Modell nicht direkt aufrufen soll.
Im Zentrum von AgentRegistry steht total_count: AtomicUsize. Spawnen mehrere Agenten nebeneinander, wird per compare_exchange_weak um den Slot gespint; wer nicht erwischt, erhält AgentLimitReached. SpawnReservation hält Arc<AgentRegistry> und führt im drop automatisch fetch_sub(1) aus — ein Rust-Idiom, damit kein vergessenes manuelles Release zurückbleibt.
Wichtige Dateien
codex-rs/core/src/function_tool.rs:1-1 — Die gesamte Datei ist eine Zeile pub use codex_tools::FunctionCallError; der alte Import-Einstieg von core.codex-rs/tools/src/lib.rs:1-107 — Wurzel des codex-tools-Crates; listet alle öffentlichen Re-Exports.codex-rs/tools/src/tool_spec.rs:15-51 — ToolSpec-Enum, serde-Tags der fünf Werkzeugtypen.codex-rs/tools/src/tool_definition.rs:7-26 — ToolDefinition, Werkzeug-Metadaten, mit into_deferred-Umwandlung.codex-rs/tools/src/tool_executor.rs:14-69 — ToolExposure und ToolExecutor-Trait, Runtime-Vertrag.codex-rs/tools/src/tool_call.rs:62-101 — ToolEnvironment und ToolCall, vollständiger Kontext eines Aufrufs.codex-rs/core/src/agent/registry.rs:22-96 — AgentRegistry und reserve_spawn_slot, CAS-Slot-Verwaltung.codex-rs/core/src/agent/registry.rs:279-316 — SpawnReservation, RAII-Freigabe des Spawn-Slots.codex-rs/core/src/agent/builtins/awaiter.toml:1-40 — TOML-Konfiguration des eingebauten awaiter-Agenten; definiert die Verhaltensregeln des Sub-Agent.ToolSpec serialisiert direkt in die JSON-Form der Responses API; #[serde(tag = "type")] sorgt dafür, dass jede Variante beim Serialisieren ein type-Feld mitbekommt. Das ist der minimale Vertrag zwischen codex und 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),
}Das ToolExecutor-Trait bindet „Werkzeug-Spec" und „Ausführungslogik" zusammen. handle liefert eine boxed Future mit einheitlicher Signatur; die konkrete Implementierung kann ein Shell-Kommando, apply_patch, ein MCP-Call oder ein Extension-Callback sein. exposure() ist per Default Direct; deferred Werkzeuge überschreiben es:
// 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<'_>;
}Das CAS-Spint um den Slot in AgentRegistry ist ein typisches Muster: Ordering::AcqRel für compare_exchange, bei Fehlschlag neuesten Wert neu lesen und weiter loopen. Das ist leichter als ein Mutex und passt zu häufigem 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,
}
}
}Datenfluss
Grenzen und Fehler
function_arguments-Typ passt nicht:ToolCall::function_argumentsprüft, dasspayloadeinToolPayload::Functionist; andernfallsFunctionCallError::Fatalmit dem Hinweis „tool invoked with incompatible payload" (codex-rs/tools/src/tool_call.rs:123-133).- Nickname-Pool erschöpft: Ist
reserve_agent_nicknameaufgebraucht, wirdused_agent_nicknamesgeleert undnickname_reset_count += 1inkrementiert; nachfolgende Nicknames bekommen Suffixe wie „the 2nd" / „the 3rd"; ist auch der Pool endgültig leer, liefert erUnsupportedOperation(codex-rs/core/src/agent/registry.rs:187-225). - Agent-Pfad-Kollision:
reserve_agent_pathliefert bei schon vorhandenem PfadUnsupportedOperation; der Aufrufer muss vor dem Spawn prüfen (codex-rs/core/src/agent/registry.rs:227-244). max_threads-Limit:reserve_spawn_slotliefert bei MisserfolgAgentLimitReached { max_threads }; die Extension muss diesen Fehler behandeln, statt endlos zu wiederholen (codex-rs/core/src/agent/registry.rs:82-96).DirectModelOnlyund Code-Modus:is_directfasstDirectundDirectModelOnlyals direct zusammen, aber in der verschachtelten Sub-Sitzung des Code-Modus ist nurDirectals verschachteltes Werkzeug erlaubt; das bestimmt, ob die Spec incollect_code_mode_tool_definitionseingeht (codex-rs/tools/src/tool_executor.rs:38-42).
Zusammenfassung
Die Werkzeug-Schicht trennt sauber zwischen Protokollschicht (codex-tools) und Runtime-Schicht (core/src/tools/, agent/): Die Protokollschicht kümmert sich nur um „wie man mit dem Modell spricht"; die Runtime-Schicht um „wie ausgeführt wird". Die Nebenläufigkeitskontrolle mehrerer Agenten ist in AgentRegistry auf das kleine Muster CAS + RAII verdichtet; jede Extension, die einen neuen Thread öffnen will, muss diesen Weg gehen. Für eine konkrete Werkzeugimplementierung wie apply_patch siehe apply_patch-Protokoll; wie Werkzeugaufrufe in die Hauptschleife eingebaut werden, siehe Agent-Hauptschleife.