in codex-rs/app-server/src/request_processors/thread_processor.rs [204:319]
fn validate_dynamic_tools(tools: &[ApiDynamicToolSpec]) -> Result<(), String> {
const DYNAMIC_TOOL_NAME_MAX_LEN: usize = 128;
const DYNAMIC_TOOL_NAMESPACE_MAX_LEN: usize = 64;
const DYNAMIC_TOOL_IDENTIFIER_PATTERN: &str = "^[a-zA-Z0-9_-]+$";
const RESERVED_RESPONSES_NAMESPACES: &[&str] = &[
"api_tool",
"browser",
"computer",
"container",
"file_search",
"functions",
"image_gen",
"multi_tool_use",
"python",
"python_user_visible",
"submodel_delegator",
"terminal",
"tool_search",
"web",
];
fn escape_identifier_for_error(value: &str) -> String {
value.escape_default().to_string()
}
fn validate_dynamic_tool_identifier(
value: &str,
label: &str,
max_len: usize,
) -> Result<(), String> {
if !value
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
{
return Err(format!(
"{label} must match {DYNAMIC_TOOL_IDENTIFIER_PATTERN} to match Responses API: {}",
escape_identifier_for_error(value),
));
}
if value.chars().count() > max_len {
return Err(format!(
"{label} must be at most {max_len} characters to match Responses API: {}",
escape_identifier_for_error(value),
));
}
Ok(())
}
let mut seen = HashSet::new();
for tool in tools {
let name = tool.name.trim();
if name.is_empty() {
return Err("dynamic tool name must not be empty".to_string());
}
if name != tool.name {
return Err(format!(
"dynamic tool name has leading/trailing whitespace: {}",
escape_identifier_for_error(&tool.name),
));
}
validate_dynamic_tool_identifier(name, "dynamic tool name", DYNAMIC_TOOL_NAME_MAX_LEN)?;
if name == "mcp" || name.starts_with("mcp__") {
return Err(format!("dynamic tool name is reserved: {name}"));
}
let namespace = tool.namespace.as_deref().map(str::trim);
if let Some(namespace) = namespace {
if namespace.is_empty() {
return Err(format!(
"dynamic tool namespace must not be empty for {name}"
));
}
if Some(namespace) != tool.namespace.as_deref() {
return Err(format!(
"dynamic tool namespace has leading/trailing whitespace for {name}: {namespace}",
name = escape_identifier_for_error(name),
namespace = escape_identifier_for_error(namespace),
));
}
validate_dynamic_tool_identifier(
namespace,
"dynamic tool namespace",
DYNAMIC_TOOL_NAMESPACE_MAX_LEN,
)?;
if namespace == "mcp" || namespace.starts_with("mcp__") {
return Err(format!(
"dynamic tool namespace is reserved for {name}: {namespace}"
));
}
if RESERVED_RESPONSES_NAMESPACES.contains(&namespace) {
return Err(format!(
"dynamic tool namespace collides with a reserved Responses API namespace for {name}: {namespace}",
));
}
}
if !seen.insert((namespace, name)) {
if let Some(namespace) = namespace {
return Err(format!(
"duplicate dynamic tool name in namespace {namespace}: {name}"
));
}
return Err(format!("duplicate dynamic tool name: {name}"));
}
if tool.defer_loading && namespace.is_none() {
return Err(format!(
"deferred dynamic tool must include a namespace: {name}"
));
}
if let Err(err) = codex_tools::parse_tool_input_schema(&tool.input_schema) {
return Err(format!(
"dynamic tool input schema is not supported for {name}: {err}"
));
}
}
Ok(())
}