Wire incremental exec tool updates into Code Mode sessions (#60163)

## Why

Changes to nested tools should update the model's catalog without repeating the full `functions.exec` declaration or rewriting retained context.

## What changed

Pass structured exec definitions through the tool router into the session's tool world state. Enable granular updates for Responses Lite in `CodeModeOnly` when both `IncrementalTools` and `IncrementalExecTools` are enabled.

Append changed tool definitions, namespace instructions, and removals as developer messages. Keep common exec instructions and shared types frozen until compaction, then install the latest full catalog.

## Testing

Add feature-gating coverage and a scenario snapshot covering tool additions and removals, namespace instruction changes, eager/deferred transitions, re-enabling tools, retained context, and catalog restoration after compaction.

GitOrigin-RevId: bbf850d4e5018388eeb4c09345d885d776962e09
This commit is contained in:
Alex Kira
2026-11-07 19:26:20 +00:00
committed by copyberry
parent 2bc8e7f545
commit 33b3f80a09
13 changed files with 882 additions and 21 deletions
+1
View File
@@ -497,6 +497,7 @@ mod tests {
Vec::new(),
ToolMode::CodeModeOnly,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
+2
View File
@@ -831,6 +831,7 @@ mod tests {
Vec::new(),
ToolMode::Direct,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
@@ -1019,6 +1020,7 @@ mod tests {
Vec::new(),
ToolMode::Direct,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
@@ -43,6 +43,7 @@ async fn spawned_dispatch_preserves_caller_context(level: LevelFilter) -> anyhow
Vec::new(),
ToolMode::Direct,
BTreeMap::new(),
/*exec_definitions*/ None,
/*tool_namespaces_info*/ None,
&[],
));
+7
View File
@@ -101,6 +101,7 @@ pub struct ToolRouter {
model_visible_specs: Arc<[ToolSpec]>,
tool_mode: ToolMode,
code_mode_tool_names: BTreeMap<String, ToolName>,
exec_definitions: Option<Arc<Vec<codex_code_mode::ExecDefinition>>>,
tool_namespaces_info: Option<TurnToolNamespacesInfo>,
can_manage_children: bool,
}
@@ -141,6 +142,7 @@ impl ToolRouter {
model_visible_specs: Vec<ToolSpec>,
tool_mode: ToolMode,
code_mode_tool_names: BTreeMap<String, ToolName>,
exec_definitions: Option<Arc<Vec<codex_code_mode::ExecDefinition>>>,
tool_namespaces_info: Option<TurnToolNamespacesInfo>,
child_management_tools: &[ToolName],
) -> Self {
@@ -149,6 +151,7 @@ impl ToolRouter {
model_visible_specs: model_visible_specs.into(),
tool_mode,
code_mode_tool_names,
exec_definitions,
tool_namespaces_info,
can_manage_children: false,
};
@@ -163,6 +166,10 @@ impl ToolRouter {
Arc::clone(&self.model_visible_specs)
}
pub(crate) fn exec_definitions(&self) -> Option<Arc<Vec<codex_code_mode::ExecDefinition>>> {
self.exec_definitions.clone()
}
pub(crate) fn tool_mode(&self) -> ToolMode {
self.tool_mode
}
+28 -15
View File
@@ -497,7 +497,7 @@ pub(crate) fn finalize_tool_router(
registry.mcp_namespaces(),
)
.map_err(|error| CodexErrorDetails::InvalidRequest(error.to_string()))?;
let code_mode_tool_names =
let (code_mode_tool_names, exec_definitions) =
register_code_mode_executors(turn_context, model_info, &mut registry, &indirect_prefixes);
let include_tool_namespaces_info = turn_context
.config
@@ -580,6 +580,7 @@ pub(crate) fn finalize_tool_router(
model_visible_specs,
tool_mode,
code_mode_tool_names,
exec_definitions,
tool_namespaces_info,
&child_management_tools,
))
@@ -1052,10 +1053,13 @@ fn register_code_mode_executors(
model_info: &ModelInfo,
registry: &mut ToolRegistry,
indirect_prefixes: &IndirectNamespacePrefixes<'_>,
) -> BTreeMap<String, ToolName> {
) -> (
BTreeMap<String, ToolName>,
Option<Arc<Vec<codex_code_mode::ExecDefinition>>>,
) {
let tool_mode = effective_tool_mode(turn_context, model_info);
if !matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) {
return BTreeMap::new();
return (BTreeMap::new(), None);
}
let mut code_mode_tool_names = BTreeMap::new();
@@ -1109,21 +1113,30 @@ fn register_code_mode_executors(
}
enabled_tools
.sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions));
let (exec_description, _) = codex_code_mode::build_exec_tool_description_and_definitions(
&enabled_tools,
&deferred_tools,
&namespace_descriptions,
turn_context.config.code_mode.default_exec_yield_time_ms,
tool_mode == ToolMode::CodeModeOnly,
let image_detail_visibility =
if unified_image_budget_enabled(&turn_context.config.features, model_info) {
codex_code_mode::ImageDetailVisibility::Hidden
} else {
codex_code_mode::ImageDetailVisibility::Visible
},
deferred_tool_discovery,
/*incremental_exec_enabled*/ false,
model_messages.code_mode(),
);
};
let features = &turn_context.config.features;
let incremental_exec_enabled = model_info.use_responses_lite
&& features.enabled(Feature::IncrementalTools)
&& features.enabled(Feature::IncrementalExecTools)
&& tool_mode == ToolMode::CodeModeOnly;
let (exec_description, exec_definitions) =
codex_code_mode::build_exec_tool_description_and_definitions(
&enabled_tools,
&deferred_tools,
&namespace_descriptions,
turn_context.config.code_mode.default_exec_yield_time_ms,
tool_mode == ToolMode::CodeModeOnly,
image_detail_visibility,
deferred_tool_discovery,
incremental_exec_enabled,
model_messages.code_mode(),
);
let exec_definitions = incremental_exec_enabled.then(|| Arc::new(exec_definitions));
let execute_handler = CodeModeExecuteHandler::new(
create_code_mode_tool(exec_description),
code_mode_nested_tool_specs,
@@ -1135,7 +1148,7 @@ fn register_code_mode_executors(
)));
registry.prepend_trusted(Arc::new(execute_handler));
code_mode_tool_names
(code_mode_tool_names, exec_definitions)
}
#[instrument(level = "trace", skip_all, fields(tool_spec_count = specs.len()))]