Keep Code Mode tool discovery guidance stable across catalog changes (#50562)

## Why

Tool availability can change between calls. Conditioning discovery guidance on whether deferred tools are currently present changes the `exec` description when the catalog changes.

## What changed

Always include the configured discovery guidance in the Code Mode `exec` description, even when no deferred tools are present. The default guidance explains how to find tools in `ALL_TOOLS` and notes that availability can change between calls. Custom guidance and explicit empty overrides remain supported.

## Testing

Add regression coverage for stable guidance with empty and populated deferred tool catalogs in both Code Mode configurations, including custom and empty overrides. Update tool-description assertions and request snapshots.

GitOrigin-RevId: 086ea19a48e3c51ec8c429a619676a936d658721
This commit is contained in:
Ahmed Ibrahim
2026-10-03 07:37:00 +00:00
committed by copyberry
parent 1a169eda11
commit 58ca099b03
29 changed files with 132 additions and 179 deletions
@@ -14,7 +14,9 @@ use crate::json_schema_types::render_json_schema_to_typescript_with_budget;
const MAX_JS_SAFE_INTEGER: u64 = (1_u64 << 53) - 1;
const DEFERRED_NESTED_TOOLS_GUIDANCE: &str = r#"Some deferred nested tools may be omitted from this description. They are still available on the global `tools` object and listed in `ALL_TOOLS`.
To find one, filter `ALL_TOOLS` by `name` and `description`."#;
To find one, filter `ALL_TOOLS` by `name` and `description`.
Tool availability can change between calls."#;
const LEGACY_IMAGE_HELPER_DESCRIPTION: &str = r#"`image(imageUrlOrItem: string | { image_url: string; detail?: "auto" | "low" | "high" | "original" | null } | ImageContent, detail?: "auto" | "low" | "high" | "original" | null)`: Appends an image item. `image_url` should be a base64-encoded `data:` URL. To forward an MCP tool image, pass an individual `ImageContent` block from `result.content`, for example `image(result.content[0])`. MCP image blocks may request detail with `_meta: { "codex/imageDetail": "original" }`. When provided, the second `detail` argument overrides any detail embedded in the first argument."#;
const UNIFIED_IMAGE_HELPER_DESCRIPTION: &str = r#"`image(imageUrlOrItem: string | { image_url: string } | ImageContent)`: Appends an image item. `image_url` should be a base64-encoded `data:` URL. To forward an MCP tool image, pass an individual `ImageContent` block from `result.content`, for example `image(result.content[0])`."#;
const EXEC_DESCRIPTION_TEMPLATE: &str = r#"Run JavaScript code to orchestrate/compose tool calls
@@ -268,7 +270,7 @@ pub enum ImageDetailVisibility {
pub fn build_exec_tool_description(
enabled_tools: &[ToolDefinition],
deferred_tools: &[ToolDefinition],
_deferred_tools: &[ToolDefinition],
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
default_exec_yield_time_ms: u64,
code_mode_only: bool,
@@ -292,13 +294,11 @@ pub fn build_exec_tool_description(
if !description.is_empty() {
sections.push(description);
}
if !deferred_tools.is_empty() {
let guidance = messages
.and_then(|messages| messages.deferred_nested_tools_guidance.as_deref())
.unwrap_or(DEFERRED_NESTED_TOOLS_GUIDANCE);
if !guidance.is_empty() {
sections.push(guidance.to_string());
}
let guidance = messages
.and_then(|messages| messages.deferred_nested_tools_guidance.as_deref())
.unwrap_or(DEFERRED_NESTED_TOOLS_GUIDANCE);
if !guidance.is_empty() {
sections.push(guidance.to_string());
}
if !code_mode_only {
return sections.join("\n\n");
@@ -46,7 +46,7 @@ fn exec_override_renders_only_known_literal_placeholders() {
assert_eq!(
description,
format!(
" \nDefaults to 10000 ms. 4567 ms.\n{image_helper}\n{{{{ unknown }}}} {{{{default_exec_yield_time_ms}}}}\t "
" \nDefaults to 10000 ms. 4567 ms.\n{image_helper}\n{{{{ unknown }}}} {{{{default_exec_yield_time_ms}}}}\t \n\n{DEFERRED_NESTED_TOOLS_GUIDANCE}"
),
);
}
@@ -77,6 +77,7 @@ fn exec_override_preserves_empty_and_whitespace_only_text() {
),
[
description_override,
"No deferred tools; omit this.",
"Shared MCP Types:\n```ts\nNo MCP tools; omit this.\n```"
]
.into_iter()
@@ -208,3 +209,47 @@ fn mcp_types_stay_stable_when_a_deferred_tool_changes_its_output_schema() {
assert_eq!(before, render(&tool, preamble));
}
}
#[test]
fn nested_guidance_survives_catalog_changes_and_respects_overrides() {
let deferred_tools = [ToolDefinition {
name: "deferred_echo".to_string(),
tool_name: ToolName::plain("deferred_echo"),
description: "Echo a value.".to_string(),
kind: CodeModeToolKind::Function,
input_schema: None,
input_schema_max_bytes: None,
output_schema: None,
}];
let render = |tools: &[ToolDefinition], code_mode_only, guidance: Option<&str>| {
build_exec_tool_description(
&[],
tools,
&BTreeMap::new(),
crate::DEFAULT_EXEC_YIELD_TIME_MS,
code_mode_only,
ImageDetailVisibility::Visible,
Some(&CodeModeToolMessages {
exec: Some(ToolMessage {
description: Some(String::new()),
..Default::default()
}),
deferred_nested_tools_guidance: guidance.map(str::to_string),
mcp_typescript_preamble: Some(String::new()),
..Default::default()
}),
)
};
for code_mode_only in [false, true] {
for (guidance, expected) in [
(None, DEFERRED_NESTED_TOOLS_GUIDANCE),
(Some("Catalog discovery."), "Catalog discovery."),
(Some(""), ""),
] {
let prompt = render(&[], code_mode_only, guidance);
assert_eq!(prompt, expected);
assert_eq!(prompt, render(&deferred_tools, code_mode_only, guidance));
}
}
assert!(DEFERRED_NESTED_TOOLS_GUIDANCE.contains("Tool availability can change between calls"));
}