mirror of
https://github.com/openai/codex.git
synced 2026-05-25 05:24:37 +00:00
## Why Extensions that need to track runtime progress currently have no typed host signal for tool execution. The goal extension in particular needs to observe tool attempts without inspecting tool payloads, owning tool implementations, or staying coupled to core-only runtime plumbing. This adds a narrow lifecycle contributor API for host-owned tool execution: extensions can observe when an accepted tool call starts and how it finishes, while policy hooks and tool handlers continue to own payload rewriting, blocking, and execution. Relevant code: - [`ToolLifecycleContributor`](3ad2850ffc/codex-rs/ext/extension-api/src/contributors.rs (L119)) defines the extension-facing observer contract. - [`tool_lifecycle.rs`](3ad2850ffc/codex-rs/ext/extension-api/src/contributors/tool_lifecycle.rs) defines the typed start/finish inputs, source, and outcome enums. - [`notify_tool_start` / `notify_tool_finish`](3ad2850ffc/codex-rs/core/src/tools/lifecycle.rs) bridges core tool dispatch into the extension registry. ## What Changed - Added `ToolLifecycleContributor` to `codex-extension-api`, including: - `ToolStartInput` - `ToolFinishInput` - `ToolCallSource` - `ToolCallOutcome` - Added registration and lookup support on `ExtensionRegistryBuilder` / `ExtensionRegistry`. - Wired core tool dispatch to notify lifecycle contributors for: - accepted tool starts - completed tool calls, including the tool output success marker - pre-tool-use blocks - failures before or after the handler runs - cancellation/abort in the parallel tool path - Registered the goal extension as a lifecycle contributor and added the outcome filter it will use for goal progress accounting. ## Test Coverage - Added `dispatch_notifies_tool_lifecycle_contributors` to cover lifecycle notification ordering and outcomes for successful and handler-failed tool calls.
99 lines
2.9 KiB
Rust
99 lines
2.9 KiB
Rust
use codex_extension_api::ToolCallOutcome;
|
|
use codex_extension_api::ToolCallSource as ExtensionToolCallSource;
|
|
use codex_extension_api::ToolFinishInput;
|
|
use codex_extension_api::ToolStartInput;
|
|
use codex_tools::ToolName;
|
|
|
|
use crate::session::session::Session;
|
|
use crate::session::turn_context::TurnContext;
|
|
use crate::tools::context::ToolCallSource;
|
|
use crate::tools::context::ToolInvocation;
|
|
|
|
pub(crate) async fn notify_tool_start(invocation: &ToolInvocation) {
|
|
for contributor in invocation
|
|
.session
|
|
.services
|
|
.extensions
|
|
.tool_lifecycle_contributors()
|
|
{
|
|
contributor
|
|
.on_tool_start(ToolStartInput {
|
|
session_store: &invocation.session.services.session_extension_data,
|
|
thread_store: &invocation.session.services.thread_extension_data,
|
|
turn_store: invocation.turn.extension_data.as_ref(),
|
|
turn_id: invocation.turn.sub_id.as_str(),
|
|
call_id: invocation.call_id.as_str(),
|
|
tool_name: &invocation.tool_name,
|
|
source: extension_tool_call_source(invocation.source.clone()),
|
|
})
|
|
.await;
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn notify_tool_finish(invocation: &ToolInvocation, outcome: ToolCallOutcome) {
|
|
notify_tool_finish_parts(
|
|
invocation.session.as_ref(),
|
|
invocation.turn.as_ref(),
|
|
invocation.call_id.as_str(),
|
|
&invocation.tool_name,
|
|
invocation.source.clone(),
|
|
outcome,
|
|
)
|
|
.await;
|
|
}
|
|
|
|
pub(crate) async fn notify_tool_aborted(
|
|
session: &Session,
|
|
turn: &TurnContext,
|
|
call_id: &str,
|
|
tool_name: &ToolName,
|
|
source: ToolCallSource,
|
|
) {
|
|
notify_tool_finish_parts(
|
|
session,
|
|
turn,
|
|
call_id,
|
|
tool_name,
|
|
source,
|
|
ToolCallOutcome::Aborted,
|
|
)
|
|
.await;
|
|
}
|
|
|
|
async fn notify_tool_finish_parts(
|
|
session: &Session,
|
|
turn: &TurnContext,
|
|
call_id: &str,
|
|
tool_name: &ToolName,
|
|
source: ToolCallSource,
|
|
outcome: ToolCallOutcome,
|
|
) {
|
|
for contributor in session.services.extensions.tool_lifecycle_contributors() {
|
|
contributor
|
|
.on_tool_finish(ToolFinishInput {
|
|
session_store: &session.services.session_extension_data,
|
|
thread_store: &session.services.thread_extension_data,
|
|
turn_store: turn.extension_data.as_ref(),
|
|
turn_id: turn.sub_id.as_str(),
|
|
call_id,
|
|
tool_name,
|
|
source: extension_tool_call_source(source.clone()),
|
|
outcome,
|
|
})
|
|
.await;
|
|
}
|
|
}
|
|
|
|
fn extension_tool_call_source(source: ToolCallSource) -> ExtensionToolCallSource {
|
|
match source {
|
|
ToolCallSource::Direct => ExtensionToolCallSource::Direct,
|
|
ToolCallSource::CodeMode {
|
|
cell_id,
|
|
runtime_tool_call_id,
|
|
} => ExtensionToolCallSource::CodeMode {
|
|
cell_id,
|
|
runtime_tool_call_id,
|
|
},
|
|
}
|
|
}
|