mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
## What changed Render stable entries for nested tool definitions and namespace instructions alongside the full `functions.exec` description. Add optional catalog tracking that emits complete replacements for added or changed entries and explicit notices for removed entries, while keeping the existing exec declaration unchanged. Preserve entry hashes while exec is unavailable, then restore the full catalog and withdraw removed entries when it returns. Keep the existing snapshot format for ordinary tool catalogs. This change provides the rendering and state support; runtime activation remains disabled. ## Testing Add tests for definition rendering and namespace identity, batched additions and changes, removal notices, unchanged catalogs, and full catalog restoration after exec is hidden. GitOrigin-RevId: f472bd3e8e770a8cad9663c72e290abe7d0473be
320 lines
12 KiB
Rust
320 lines
12 KiB
Rust
//! Tests catalog exec template rendering and runtime description composition.
|
|
|
|
use super::CodeModeToolKind;
|
|
use super::DEFERRED_NESTED_TOOLS_GUIDANCE;
|
|
use super::DeferredToolDiscovery;
|
|
use super::ImageDetailVisibility;
|
|
use super::LEGACY_IMAGE_HELPER_DESCRIPTION;
|
|
use super::MCP_TYPESCRIPT_PREAMBLE;
|
|
use super::TOOL_SEARCH_GUIDANCE;
|
|
use super::ToolDefinition;
|
|
use super::UNIFIED_IMAGE_HELPER_DESCRIPTION;
|
|
use super::build_exec_tool_description_and_definitions;
|
|
use codex_protocol::ToolName;
|
|
use codex_protocol::openai_models::CodeModeToolMessages;
|
|
use codex_protocol::openai_models::ToolMessage;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
use std::collections::BTreeMap;
|
|
|
|
#[test]
|
|
fn exec_override_renders_only_known_literal_placeholders() {
|
|
for (image_detail_visibility, image_helper) in [
|
|
(
|
|
ImageDetailVisibility::Visible,
|
|
LEGACY_IMAGE_HELPER_DESCRIPTION,
|
|
),
|
|
(
|
|
ImageDetailVisibility::Hidden,
|
|
UNIFIED_IMAGE_HELPER_DESCRIPTION,
|
|
),
|
|
] {
|
|
let (description, _) = build_exec_tool_description_and_definitions(
|
|
&[],
|
|
&[],
|
|
&BTreeMap::new(),
|
|
/*default_exec_yield_time_ms*/ 4567,
|
|
/*code_mode_only*/ false,
|
|
image_detail_visibility,
|
|
DeferredToolDiscovery::Catalog,
|
|
/*incremental_exec_enabled*/ false,
|
|
Some(&CodeModeToolMessages {
|
|
exec: Some(ToolMessage {
|
|
description: Some(" \nDefaults to 10000 ms. {{ default_exec_yield_time_ms }} ms.\n{{ image_helper }}\n{{ unknown }} {{default_exec_yield_time_ms}}\t ".to_string()),
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
}),
|
|
);
|
|
|
|
assert_eq!(
|
|
description,
|
|
format!(
|
|
" \nDefaults to 10000 ms. 4567 ms.\n{image_helper}\n{{{{ unknown }}}} {{{{default_exec_yield_time_ms}}}}\t \n\n{DEFERRED_NESTED_TOOLS_GUIDANCE}"
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn exec_override_preserves_empty_and_whitespace_only_text() {
|
|
for description_override in ["", " \n\t "] {
|
|
assert_eq!(
|
|
build_exec_tool_description_and_definitions(
|
|
&[],
|
|
&[],
|
|
&BTreeMap::new(),
|
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
|
/*code_mode_only*/ true,
|
|
ImageDetailVisibility::Visible,
|
|
DeferredToolDiscovery::Catalog,
|
|
/*incremental_exec_enabled*/ false,
|
|
Some(&CodeModeToolMessages {
|
|
exec: Some(ToolMessage {
|
|
description: Some(description_override.to_string()),
|
|
..Default::default()
|
|
}),
|
|
deferred_nested_tools_guidance: Some(
|
|
"No deferred tools; omit this.".to_string()
|
|
),
|
|
mcp_typescript_preamble: Some("No MCP tools; omit this.".to_string()),
|
|
..Default::default()
|
|
}),
|
|
)
|
|
.0,
|
|
[
|
|
description_override,
|
|
"No deferred tools; omit this.",
|
|
"Shared MCP Types:\n```ts\nNo MCP tools; omit this.\n```"
|
|
]
|
|
.into_iter()
|
|
.filter(|section| !section.is_empty())
|
|
.collect::<Vec<_>>()
|
|
.join("\n\n"),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn exec_override_preserves_runtime_sections() {
|
|
let enabled_tools = [ToolDefinition {
|
|
name: "alpha".to_string(),
|
|
tool_name: ToolName::plain("alpha"),
|
|
description: "First tool".to_string(),
|
|
kind: CodeModeToolKind::Function,
|
|
input_schema: None,
|
|
input_schema_max_bytes: None,
|
|
output_schema: None,
|
|
}];
|
|
let deferred_tools = [ToolDefinition {
|
|
name: "mcp__sample__beta".to_string(),
|
|
tool_name: ToolName::namespaced("mcp__sample__", "beta"),
|
|
description: "Deferred tool".to_string(),
|
|
kind: CodeModeToolKind::Function,
|
|
input_schema: None,
|
|
input_schema_max_bytes: None,
|
|
output_schema: Some(json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"content": { "type": "array", "items": { "type": "object" } },
|
|
"isError": { "type": "boolean" },
|
|
"_meta": { "type": "object" }
|
|
}
|
|
})),
|
|
}];
|
|
let declaration = "### `alpha`\nFirst tool\n\nexec tool declaration:\n```ts\ndeclare const tools: { alpha(args: unknown): Promise<unknown>; };\n```";
|
|
for (code_mode_only, guidance, preamble, expected) in [
|
|
(true, Some(""), Some(""), declaration.to_string()),
|
|
(
|
|
true,
|
|
None,
|
|
Some(""),
|
|
format!("{DEFERRED_NESTED_TOOLS_GUIDANCE}\n\n{declaration}"),
|
|
),
|
|
(
|
|
true,
|
|
Some(""),
|
|
None,
|
|
format!("Shared MCP Types:\n```ts\n{MCP_TYPESCRIPT_PREAMBLE}\n```\n\n{declaration}"),
|
|
),
|
|
(
|
|
true,
|
|
Some(" {{ image_helper }}\n"),
|
|
Some(" {{ default_exec_yield_time_ms }}\n"),
|
|
format!(
|
|
" {{{{ image_helper }}}}\n\n\nShared MCP Types:\n```ts\n {{{{ default_exec_yield_time_ms }}}}\n\n```\n\n{declaration}"
|
|
),
|
|
),
|
|
(
|
|
false,
|
|
Some("Catalog discovery."),
|
|
Some("No types outside Code Mode Only."),
|
|
"Catalog discovery.".to_string(),
|
|
),
|
|
] {
|
|
assert_eq!(
|
|
build_exec_tool_description_and_definitions(
|
|
&enabled_tools,
|
|
&deferred_tools,
|
|
&BTreeMap::new(),
|
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
|
code_mode_only,
|
|
ImageDetailVisibility::Visible,
|
|
DeferredToolDiscovery::Catalog,
|
|
/*incremental_exec_enabled*/ false,
|
|
Some(&CodeModeToolMessages {
|
|
exec: Some(ToolMessage {
|
|
description: Some(String::new()),
|
|
..Default::default()
|
|
}),
|
|
deferred_nested_tools_guidance: guidance.map(str::to_string),
|
|
mcp_typescript_preamble: preamble.map(str::to_string),
|
|
..Default::default()
|
|
}),
|
|
)
|
|
.0,
|
|
expected,
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ranked_search_guidance_honors_overrides() {
|
|
let deferred_tools = [ToolDefinition {
|
|
name: "deferred_tool".to_string(),
|
|
tool_name: ToolName::plain("deferred_tool"),
|
|
description: "Deferred tool".to_string(),
|
|
kind: CodeModeToolKind::Function,
|
|
input_schema: None,
|
|
input_schema_max_bytes: None,
|
|
output_schema: None,
|
|
}];
|
|
for code_mode_only in [false, true] {
|
|
for (guidance, expected) in [
|
|
(
|
|
None,
|
|
if code_mode_only {
|
|
DEFERRED_NESTED_TOOLS_GUIDANCE.to_string()
|
|
} else {
|
|
format!("{DEFERRED_NESTED_TOOLS_GUIDANCE}\n\n{TOOL_SEARCH_GUIDANCE}")
|
|
},
|
|
),
|
|
(Some(""), String::new()),
|
|
(Some("Catalog discovery."), "Catalog discovery.".to_string()),
|
|
] {
|
|
let (description, _) = build_exec_tool_description_and_definitions(
|
|
&[],
|
|
&deferred_tools,
|
|
&BTreeMap::new(),
|
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
|
code_mode_only,
|
|
ImageDetailVisibility::Visible,
|
|
DeferredToolDiscovery::RankedSearch,
|
|
/*incremental_exec_enabled*/ false,
|
|
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()
|
|
}),
|
|
);
|
|
assert_eq!(description, expected);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn mcp_types_stay_stable_when_a_deferred_tool_changes_its_output_schema() {
|
|
let mut tool = ToolDefinition {
|
|
name: "sample".to_string(),
|
|
tool_name: ToolName::plain("sample"),
|
|
description: "Deferred tool".to_string(),
|
|
kind: CodeModeToolKind::Function,
|
|
input_schema: None,
|
|
input_schema_max_bytes: None,
|
|
output_schema: None,
|
|
};
|
|
let render = |tool: &ToolDefinition, preamble: Option<&str>| {
|
|
build_exec_tool_description_and_definitions(
|
|
&[],
|
|
std::slice::from_ref(tool),
|
|
&BTreeMap::new(),
|
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
|
/*code_mode_only*/ true,
|
|
ImageDetailVisibility::Visible,
|
|
DeferredToolDiscovery::Catalog,
|
|
/*incremental_exec_enabled*/ false,
|
|
Some(&CodeModeToolMessages {
|
|
mcp_typescript_preamble: preamble.map(str::to_string),
|
|
..Default::default()
|
|
}),
|
|
)
|
|
.0
|
|
};
|
|
// Keep one deferred tool throughout to isolate this from the independent
|
|
// discovery guidance transition when the entire deferred catalog empties.
|
|
for preamble in [None, Some("type CustomMcpResult = string;"), Some("")] {
|
|
tool.output_schema = None;
|
|
let before = render(&tool, preamble);
|
|
tool.output_schema = Some(json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"content": { "type": "array", "items": { "type": "object" } },
|
|
"isError": { "type": "boolean" },
|
|
"_meta": { "type": "object" }
|
|
}
|
|
}));
|
|
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_and_definitions(
|
|
&[],
|
|
tools,
|
|
&BTreeMap::new(),
|
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
|
code_mode_only,
|
|
ImageDetailVisibility::Visible,
|
|
DeferredToolDiscovery::Catalog,
|
|
/*incremental_exec_enabled*/ false,
|
|
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()
|
|
}),
|
|
)
|
|
.0
|
|
};
|
|
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"));
|
|
}
|