Always put Code Mode tool descriptions before namespace descriptions (#57462)

## What changed

Make tool descriptions precede namespace descriptions in Code Mode execution and tool search. Mark `code_mode_tool_description_first` as removed and simplify cached definitions to depend only on the input schema budget.

## Testing

Update Code Mode integration assertions and catalog snapshots to expect tool descriptions before namespace descriptions.

GitOrigin-RevId: 1344d4c264d1d2b18ee0388a064b6813ca81f770
This commit is contained in:
swastik-oai
2026-10-27 14:14:16 +00:00
committed by copyberry
parent 50983c7a93
commit 2ebf42f269
11 changed files with 35 additions and 105 deletions
@@ -4,7 +4,6 @@ use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor;
use codex_features::Feature;
use codex_prompts::ResolvedModelMessages;
use codex_tools::IndirectNamespacePrefixes;
use codex_tools::ToolName;
@@ -56,18 +55,12 @@ impl CodeModeExecuteHandler {
.config
.code_mode
.tool_input_schema_max_bytes;
let tool_description_first = step_context
.turn
.config
.features
.enabled(Feature::CodeModeToolDescriptionFirst);
let mut enabled_tools = Vec::with_capacity(self.nested_tool_specs.len());
for (spec, cached_runtime) in &self.nested_tool_specs {
let definitions = prepare_code_mode_tool_definitions(
cached_runtime.as_deref(),
|| Cow::Borrowed(spec.as_ref()),
code_mode_input_schema_max_bytes,
tool_description_first,
);
match definitions {
Cow::Borrowed(definitions) => enabled_tools.extend_from_slice(definitions),
@@ -1,6 +1,5 @@
//! Prepares nested tool definitions for execution and search.
//! Cached definitions stay borrowed; rendered schemas are dropped after augmentation.
//! Description order follows the session-static feature setting.
use crate::tools::registry::CoreToolRuntime;
use codex_code_mode::ToolDefinition;
@@ -11,12 +10,10 @@ pub(crate) fn prepare_code_mode_tool_definitions<'a>(
runtime: Option<&'a dyn CoreToolRuntime>,
spec: impl FnOnce() -> Cow<'a, ToolSpec>,
code_mode_input_schema_max_bytes: Option<usize>,
tool_description_first: bool,
) -> Cow<'a, [ToolDefinition]> {
if let Some(definitions) = runtime.and_then(|runtime| {
runtime
.cached_code_mode_definitions(code_mode_input_schema_max_bytes, tool_description_first)
}) {
if let Some(definitions) = runtime
.and_then(|runtime| runtime.cached_code_mode_definitions(code_mode_input_schema_max_bytes))
{
return Cow::Borrowed(definitions);
}
@@ -24,7 +21,6 @@ pub(crate) fn prepare_code_mode_tool_definitions<'a>(
let mut definitions = codex_tools::collect_code_mode_tool_definitions(
std::iter::once(spec.as_ref()),
code_mode_input_schema_max_bytes,
tool_description_first,
);
for definition in &mut definitions {
definition.input_schema = None;
+16 -46
View File
@@ -90,8 +90,7 @@ impl McpHandlerDefinition {
pub struct McpHandler {
definition: Arc<McpHandlerDefinition>,
code_mode_tool_definitions:
OnceLock<(Option<usize>, bool, Vec<codex_code_mode::ToolDefinition>)>,
code_mode_tool_definitions: OnceLock<(Option<usize>, Vec<codex_code_mode::ToolDefinition>)>,
}
#[cfg(test)]
@@ -330,29 +329,19 @@ impl CoreToolRuntime for McpHandler {
fn cached_code_mode_definitions(
&self,
code_mode_input_schema_max_bytes: Option<usize>,
tool_description_first: bool,
) -> Option<&[codex_code_mode::ToolDefinition]> {
let (cached_budget, cached_tool_description_first, definitions) =
self.code_mode_tool_definitions.get_or_init(|| {
let mut definitions = codex_tools::collect_code_mode_tool_definitions(
std::iter::once(self.definition.spec.as_ref()),
code_mode_input_schema_max_bytes,
tool_description_first,
);
for definition in &mut definitions {
definition.input_schema = None;
definition.output_schema = None;
}
(
code_mode_input_schema_max_bytes,
tool_description_first,
definitions,
)
});
// Reuse definitions only when both rendering inputs match.
(*cached_budget == code_mode_input_schema_max_bytes
&& *cached_tool_description_first == tool_description_first)
.then_some(definitions.as_slice())
let (cached_budget, definitions) = self.code_mode_tool_definitions.get_or_init(|| {
let mut definitions = codex_tools::collect_code_mode_tool_definitions(
std::iter::once(self.definition.spec.as_ref()),
code_mode_input_schema_max_bytes,
);
for definition in &mut definitions {
definition.input_schema = None;
definition.output_schema = None;
}
(code_mode_input_schema_max_bytes, definitions)
});
(*cached_budget == code_mode_input_schema_max_bytes).then_some(definitions.as_slice())
}
fn wait_until_ready<'a>(&'a self, session: &'a Arc<Session>) -> Option<BoxFuture<'a, ()>> {
@@ -905,36 +894,17 @@ mod tests {
));
let first = handler
.cached_code_mode_definitions(
/*code_mode_input_schema_max_bytes*/ None,
/*tool_description_first*/ false,
)
.cached_code_mode_definitions(/*code_mode_input_schema_max_bytes*/ None)
.expect("MCP definitions should be cached");
assert_eq!(first.len(), 1);
assert!(first[0].input_schema.is_none());
assert!(first[0].output_schema.is_none());
assert!(
handler
.cached_code_mode_definitions(
/*code_mode_input_schema_max_bytes*/ None,
/*tool_description_first*/ true,
)
.is_none()
);
let second = handler
.cached_code_mode_definitions(
/*code_mode_input_schema_max_bytes*/ None,
/*tool_description_first*/ false,
)
.cached_code_mode_definitions(/*code_mode_input_schema_max_bytes*/ None)
.expect("MCP definitions should be cached");
assert!(std::ptr::eq(first, second));
assert!(
handler
.cached_code_mode_definitions(Some(32_000), /*tool_description_first*/ false)
.is_none()
);
assert!(handler.cached_code_mode_definitions(Some(32_000)).is_none());
}
#[test]
@@ -15,7 +15,6 @@ use bm25::Embedder;
use bm25::EmbedderBuilder;
use bm25::Language;
use bm25::Scorer;
use codex_features::Feature;
use codex_prompts::ResolvedModelMessages;
use codex_tools::IndirectNamespacePrefixes;
use codex_tools::JsonToolOutput;
@@ -260,11 +259,6 @@ impl ToolSearchHandler {
.config
.code_mode
.tool_input_schema_max_bytes,
step_context
.turn
.config
.features
.enabled(Feature::CodeModeToolDescriptionFirst),
&indirect_prefixes,
);
return Ok(boxed_tool_output(JsonToolOutput::new(serde_json::json!({
@@ -292,7 +286,6 @@ impl ToolSearchHandler {
limit: usize,
router: &ToolRouter,
code_mode_input_schema_max_bytes: Option<usize>,
tool_description_first: bool,
indirect_prefixes: &IndirectNamespacePrefixes<'_>,
) -> Vec<CodeModeSearchResult> {
let mut tools = Vec::new();
@@ -320,7 +313,6 @@ impl ToolSearchHandler {
tool.cached_runtime().map(Arc::as_ref),
|| Cow::Owned(Arc::unwrap_or_clone(tool.spec())),
code_mode_input_schema_max_bytes,
tool_description_first,
);
let Some(definition) = definitions.iter().find(|definition| {
definition.tool_name.clone().with_default_namespace() == candidate_name
+1 -2
View File
@@ -84,11 +84,10 @@ pub(crate) trait CoreToolRuntime: ToolExecutor<ToolInvocation> {
/// Returns lazily cached, augmented Code Mode definitions owned by this runtime.
/// Input and output schemas must be cleared after rendering their declarations.
/// Return None when the requested rendering inputs differ from the cached inputs.
/// Return None when the requested schema budget differs from the cached budget.
fn cached_code_mode_definitions(
&self,
_code_mode_input_schema_max_bytes: Option<usize>,
_tool_description_first: bool,
) -> Option<&[codex_code_mode::ToolDefinition]> {
None
}