mirror of
https://github.com/openai/codex.git
synced 2026-11-03 16:23:51 +00:00
Track Code Mode tool call metadata completeness (#41058)
## Why Executed-tool metadata for a Code Mode cell can span its `exec` and `wait` outputs. Consumers need to know whether that metadata contains the cell's full tool call inventory or only a partial record. ## What changed - Associate recorded tool calls with the originating Code Mode cell across `exec` and `wait` outputs. - Add a host-owned `tool_calls_complete` marker when recording finishes without dropped or truncated calls. The marker describes inventory completeness, not tool success. - Preserve the cell metadata through replay and pruning, and clear the completeness marker when prompt budgeting makes the record incomplete. ## Testing Add coverage for multi-wait cells, retries, interruptions, recording limits, argument truncation, prompt budgeting, and rejection of input-forged metadata. GitOrigin-RevId: 377014edec06aab42aadab78b8b5dccc0a1c83ba
This commit is contained in:
1 parent
f1433fc71f
commit
d5caceccb1
12 files changed
+808
-93
No files matched your search
@@ -18,8 +18,9 @@ use tracing::Instrument;
|
||||
|
||||
use super::ExecContext;
|
||||
use super::PUBLIC_TOOL_NAME;
|
||||
use super::call_nested_tool;
|
||||
use super::submit_nested_tool;
|
||||
use crate::session::step_context::StepContext;
|
||||
use crate::tools::ExecutedToolCallRecorder;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::parallel::ToolCallRuntime;
|
||||
|
||||
@@ -27,6 +28,7 @@ pub(super) struct CodeModeDispatchBroker {
|
||||
dispatch_tx: async_channel::Sender<DispatchMessage>,
|
||||
dispatch_rx: async_channel::Receiver<DispatchMessage>,
|
||||
dispatch_gates: Arc<Mutex<HashMap<CellId, CellDispatchGate>>>,
|
||||
executed_tool_calls: Option<Arc<ExecutedToolCallRecorder>>,
|
||||
}
|
||||
|
||||
struct CellDispatchGate {
|
||||
@@ -36,12 +38,13 @@ struct CellDispatchGate {
|
||||
}
|
||||
|
||||
impl CodeModeDispatchBroker {
|
||||
pub(super) fn new() -> Self {
|
||||
pub(super) fn new(executed_tool_calls: Option<Arc<ExecutedToolCallRecorder>>) -> Self {
|
||||
let (dispatch_tx, dispatch_rx) = async_channel::unbounded();
|
||||
Self {
|
||||
dispatch_tx,
|
||||
dispatch_rx,
|
||||
dispatch_gates: Arc::new(Mutex::new(HashMap::new())),
|
||||
executed_tool_calls,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +79,14 @@ impl CodeModeDispatchBroker {
|
||||
}
|
||||
|
||||
pub(super) fn close_cell(&self, cell_id: &CellId) {
|
||||
remove_dispatch_gate(&self.dispatch_gates, cell_id);
|
||||
let mut dispatch_gates = self
|
||||
.dispatch_gates
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
dispatch_gates.remove(cell_id);
|
||||
if let Some(recorder) = &self.executed_tool_calls {
|
||||
recorder.finish_cell_recording(cell_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn active_cell_ids(&self) -> Vec<CellId> {
|
||||
@@ -94,6 +104,11 @@ impl CodeModeDispatchBroker {
|
||||
step_context: Arc<StepContext>,
|
||||
tracker: SharedTurnDiffTracker,
|
||||
) -> CodeModeDispatchWorker {
|
||||
let track_completeness = exec
|
||||
.turn
|
||||
.config
|
||||
.features
|
||||
.enabled(codex_features::Feature::ExecutedToolCallMetadata);
|
||||
let tool_runtime = ToolCallRuntime::new(Arc::clone(&exec.session), step_context, tracker);
|
||||
let host = Arc::new(CoreTurnHost { exec, tool_runtime });
|
||||
let dispatch_rx = self.dispatch_rx.clone();
|
||||
@@ -148,10 +163,26 @@ impl CodeModeDispatchBroker {
|
||||
continue;
|
||||
}
|
||||
let host = Arc::clone(&host);
|
||||
let dispatch_gates = Arc::clone(&dispatch_gates);
|
||||
tokio::spawn(async move {
|
||||
let invocation = host
|
||||
.invoke_tool(invocation, cancellation_token.clone())
|
||||
.instrument(span);
|
||||
let invocation = {
|
||||
let dispatch_gate = track_completeness.then(|| {
|
||||
dispatch_gates
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
});
|
||||
if dispatch_gate.as_ref().is_some_and(|gates| {
|
||||
cancellation_token.is_cancelled()
|
||||
|| !gates.contains_key(&cell_id)
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
// Submission and cell closure share this gate.
|
||||
span.in_scope(|| {
|
||||
host.submit_tool(invocation, cancellation_token.clone())
|
||||
})
|
||||
.instrument(span)
|
||||
};
|
||||
tokio::pin!(invocation);
|
||||
let response = tokio::select! {
|
||||
biased;
|
||||
@@ -324,19 +355,19 @@ struct CoreTurnHost {
|
||||
}
|
||||
|
||||
impl CoreTurnHost {
|
||||
async fn invoke_tool(
|
||||
fn submit_tool(
|
||||
&self,
|
||||
invocation: CodeModeNestedToolCall,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Result<JsonValue, String> {
|
||||
call_nested_tool(
|
||||
) -> impl std::future::Future<Output = Result<JsonValue, String>> + Send + 'static {
|
||||
let invocation = submit_nested_tool(
|
||||
self.exec.clone(),
|
||||
self.tool_runtime.clone(),
|
||||
invocation,
|
||||
cancellation_token,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_string())
|
||||
.map_err(|error| error.to_string());
|
||||
async move { invocation?.await.map_err(|error| error.to_string()) }
|
||||
}
|
||||
|
||||
async fn notify(&self, call_id: String, cell_id: CellId, text: String) -> Result<(), String> {
|
||||
|
||||
@@ -88,7 +88,7 @@ impl CodeModeExecuteHandler {
|
||||
cell_id: cell_id.to_string(),
|
||||
});
|
||||
if let Some(executed_tool_calls) = exec.session.services.executed_tool_calls.as_ref() {
|
||||
executed_tool_calls.register_cell(&cell_id, &call_id);
|
||||
executed_tool_calls.start_cell(&cell_id, &call_id);
|
||||
}
|
||||
let runtime_cell_id = cell_id.to_string();
|
||||
let code_cell_trace = exec
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::original_image_detail::sanitize_original_image_detail as sanitize_ima
|
||||
use crate::session::session::Session;
|
||||
use crate::session::step_context::StepContext;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::ExecutedToolCallRecorder;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::context::ToolPayload;
|
||||
@@ -80,8 +81,9 @@ impl CodeModeService {
|
||||
pub(crate) fn new(
|
||||
session_provider: Arc<dyn CodeModeSessionProvider>,
|
||||
config: &CodeModeConfig,
|
||||
executed_tool_calls: Option<Arc<ExecutedToolCallRecorder>>,
|
||||
) -> Self {
|
||||
let dispatch_broker = Arc::new(CodeModeDispatchBroker::new());
|
||||
let dispatch_broker = Arc::new(CodeModeDispatchBroker::new(executed_tool_calls));
|
||||
let availability = session_provider.availability();
|
||||
Self {
|
||||
session: OnceCell::new(),
|
||||
@@ -341,12 +343,16 @@ fn truncate_code_mode_result(
|
||||
truncate_function_output_items_with_policy(&items, policy, estimate_audio_token_count)
|
||||
}
|
||||
|
||||
async fn call_nested_tool(
|
||||
// Submit synchronously so the recorder sees the call before the cell's dispatch gate closes.
|
||||
fn submit_nested_tool(
|
||||
exec: ExecContext,
|
||||
tool_runtime: ToolCallRuntime,
|
||||
invocation: CodeModeNestedToolCall,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Result<JsonValue, FunctionCallError> {
|
||||
) -> Result<
|
||||
impl std::future::Future<Output = Result<JsonValue, FunctionCallError>> + Send + 'static,
|
||||
FunctionCallError,
|
||||
> {
|
||||
let CodeModeNestedToolCall {
|
||||
cell_id,
|
||||
runtime_tool_call_id,
|
||||
@@ -380,17 +386,15 @@ async fn call_nested_tool(
|
||||
call_id: call.call_id.clone(),
|
||||
cell_id: cell_id.to_string(),
|
||||
});
|
||||
let result = tool_runtime
|
||||
.handle_tool_call_with_source(
|
||||
call,
|
||||
ToolCallSource::CodeMode {
|
||||
cell_id: cell_id.to_string(),
|
||||
runtime_tool_call_id,
|
||||
},
|
||||
cancellation_token,
|
||||
)
|
||||
.await?;
|
||||
Ok(result.code_mode_result())
|
||||
let result = tool_runtime.handle_tool_call_with_source(
|
||||
call,
|
||||
ToolCallSource::CodeMode {
|
||||
cell_id: cell_id.to_string(),
|
||||
runtime_tool_call_id,
|
||||
},
|
||||
cancellation_token,
|
||||
);
|
||||
Ok(async move { Ok(result.await?.code_mode_result()) })
|
||||
}
|
||||
|
||||
fn build_nested_tool_payload(
|
||||
|
||||
Reference in new issue
Block a user