Skip to content

[tool search] support namespaced deferred dynamic tools#18413

Merged
sayan-oai merged 6 commits into
mainfrom
codex/fix-deferred-dynamic-tool-routing
Apr 21, 2026
Merged

[tool search] support namespaced deferred dynamic tools#18413
sayan-oai merged 6 commits into
mainfrom
codex/fix-deferred-dynamic-tool-routing

Conversation

@pash-openai

@pash-openai pash-openai commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

Deferred dynamic tools need to round-trip a namespace so a tool returned by tool_search can be called through the same registry key that core uses for dispatch.

This change adds namespace support for dynamic tool specs/calls, persists it through app-server thread state, and routes dynamic tool calls by full ToolName while still sending the app the leaf tool name. Deferred dynamic tools must provide a namespace; non-deferred dynamic tools may remain top-level.

It also introduces LoadableToolSpec as the shared function-or-namespace Responses shape used by both tool_search output and dynamic tool registration, so dynamic tools use the same wrapping logic in both paths.

Validation:

  • cargo test -p codex-tools
  • cargo test -p codex-core tool_search

@pash-openai pash-openai marked this pull request as ready for review April 17, 2026 23:50
@pash-openai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pash-openai pash-openai changed the title Route self-namespaced dynamic tools Add namespaces to dynamic tools Apr 18, 2026
@pash-openai pash-openai force-pushed the codex/fix-deferred-dynamic-tool-routing branch from 9d8f242 to ad75171 Compare April 18, 2026 01:41
@pash-openai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if name == "mcp" || name.starts_with("mcp__") {
return Err(format!("dynamic tool name is reserved: {name}"));
}

P1 Badge Validate dynamic tool namespaces against reserved prefixes

validate_dynamic_tools only checks tool.name for reserved mcp prefixes and duplicate conflicts, but never validates tool.namespace. A dynamic tool can therefore use an MCP namespace/name pair and collide with real MCP tools. That key is later registered as a dynamic handler and can overwrite the existing handler, causing tool calls to be routed to the wrong backend.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/core/src/tools/handlers/dynamic.rs Outdated
@pash-openai pash-openai marked this pull request as draft April 18, 2026 01:52
Comment thread codex-rs/tools/src/dynamic_tool.rs Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this used for that we don't care about ns?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when making the actual leaf ResponsesApiTool function, we dont care about the namespace, because these leaf functions are contained in a ResponsesApiNamespace where the namespace is captured.

mcp tools do the same thing here (ignoring namespace when making ResponsesApiTool).

Comment thread codex-rs/tools/src/tool_registry_plan.rs Outdated
@pash-openai pash-openai force-pushed the codex/fix-deferred-dynamic-tool-routing branch from ad75171 to 7b3d791 Compare April 18, 2026 20:17
@pash-openai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b3d791662

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/app-server/src/codex_message_processor.rs
Comment thread codex-rs/app-server-protocol/src/protocol/v2.rs
@pash-openai pash-openai force-pushed the codex/fix-deferred-dynamic-tool-routing branch from 7b3d791 to 984d794 Compare April 18, 2026 20:42
@pash-openai

Copy link
Copy Markdown
Collaborator Author

@codex review

@pash-openai pash-openai force-pushed the codex/fix-deferred-dynamic-tool-routing branch from 984d794 to d8411aa Compare April 18, 2026 20:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 984d7945c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/tools/src/tool_registry_plan.rs Outdated
@pash-openai pash-openai force-pushed the codex/fix-deferred-dynamic-tool-routing branch 5 times, most recently from 9f7fd1f to b619c18 Compare April 18, 2026 22:22
@pash-openai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Review result: changes requested

I re-reviewed the PR state at b619c18 and found one remaining contract bug.

1) P2 — namespace is optional in Rust/JSON Schema but required in generated TS

DynamicToolSpec.namespace is declared as Option<String> in Rust, but it is missing #[ts(optional = nullable)], so the generated TypeScript type requires the field (namespace: string | null) instead of making it optional. That can break existing TS callers that omit namespace. codex-rs/app-server-protocol/src/protocol/v2.rsL607-L617 codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSpec.tsL1-L6
JSON Schema still treats namespace as optional (required excludes it), confirming the mismatch is real at the API contract boundary. codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.jsonL3867-L3891

Notes on previously flagged items

Checks run

  • git show --stat --oneline b619c1801f961033fbfb3dfb266cde4a3fbce0d0
  • nl -ba codex-rs/app-server-protocol/src/protocol/v2.rs | sed -n '600,640p'
  • nl -ba codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSpec.ts
  • nl -ba codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json | sed -n '3860,3925p'
  • nl -ba codex-rs/app-server/src/codex_message_processor.rs | sed -n '9190,9245p'
  • nl -ba codex-rs/tools/src/tool_registry_plan.rs | sed -n '552,590p'
  • nl -ba codex-rs/tools/src/tool_registry_plan_tests.rs | sed -n '1411,1465p'

No code changes were made in this pass, so no commit/PR was created.

View task →

@pash-openai pash-openai force-pushed the codex/fix-deferred-dynamic-tool-routing branch from 61927ed to 7a8eb68 Compare April 19, 2026 04:36
@pash-openai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a8eb680cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/tools/src/code_mode.rs Outdated
{
tool_name.display()
} else {
tool_name.name.clone()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve namespaces in code-mode tool names

Return a fully-qualified name for non-MCP namespaced tools. Flattening to tool_name.name makes different tools share the same code-mode identifier (e.g. shell), and later dedup/lookup logic is name-based. This can hide one tool from ALL_TOOLS and route tools.<name>(...) to the wrong handler when names collide across namespaces or with built-ins.

Useful? React with 👍 / 👎.


fn dynamic_tool_search_entry(tool: &DynamicToolSpec) -> Result<ToolSearchEntry, serde_json::Error> {
let output_tool = dynamic_tool_to_responses_api_tool(tool)?;
let output = match tool.namespace.as_ref() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because dynamic tools can now be namespaced

Comment on lines +233 to +235
Some(namespace) => format!("{namespace}_{}", tool_name.name),
None => tool_name.name.clone(),
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localized to code_mode; this makes it so the functions created from namespaced tools look reasonable (codex_apps_foo_tool rather than codex_appsfoo_tool)

@sayan-oai sayan-oai marked this pull request as ready for review April 20, 2026 23:16
@sayan-oai sayan-oai requested a review from a team as a code owner April 20, 2026 23:16
}

fn dynamic_tool_search_entry(tool: &DynamicToolSpec) -> Result<ToolSearchEntry, serde_json::Error> {
let output_tool = dynamic_tool_to_responses_api_tool(tool)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really dislike how many layers of transformations we go through to build a tool definition.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reworked to go direct from DynamicToolSpec -> ResponsesApiTool.

renamed ToolSearchOutputTool to LoadableToolSpec (lmk if there's a clearer name) as a side effect so we can use this across dynamic tool parsing in tool search and in tool registration.

Comment thread codex-rs/core/src/tools/tool_search_entry.rs Outdated
@sayan-oai sayan-oai requested a review from pakrym-oai April 21, 2026 03:10
@sayan-oai sayan-oai changed the title Add namespaces to dynamic tools Support namespaced deferred dynamic tools Apr 21, 2026
@sayan-oai sayan-oai changed the title Support namespaced deferred dynamic tools [tool search] support namespaced deferred dynamic tools Apr 21, 2026
@sayan-oai

Copy link
Copy Markdown
Collaborator

@codex review this

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df7c8410fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +228 to +234
pub fn code_mode_name_for_tool_name(tool_name: &ToolName) -> String {
match tool_name.namespace.as_deref() {
Some(namespace) if namespace.ends_with('_') || tool_name.name.starts_with('_') => {
format!("{namespace}{}", tool_name.name)
}
Some(namespace) => format!("{namespace}_{}", tool_name.name),
None => tool_name.name.clone(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make code-mode tool name mapping collision-free

code_mode_name_for_tool_name is not one-to-one. Different (namespace, name) pairs can produce the same code-mode name (e.g. (a, b_c) and (a_b, c) both map to a_b_c). Code mode uses this value as the tools object key, so later tools overwrite earlier ones and one callable becomes unreachable.

Useful? React with 👍 / 👎.

Comment on lines +9416 to +9423
if namespace == "mcp" || namespace.starts_with("mcp__") {
return Err(format!(
"dynamic tool namespace is reserved for {name}: {namespace}"
));
}
}
if !seen.insert((namespace, name)) {
if let Some(namespace) = namespace {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject dynamic tools that shadow existing namespaced tools

Validation only blocks mcp* namespaces and duplicate names within the dynamic list. It allows a dynamic tool to reuse another registered (namespace, name) pair (e.g. connector namespaces), and handler registration then overwrites the existing handler for that key. Calls can be silently routed to the wrong tool.

Useful? React with 👍 / 👎.

Comment on lines 559 to 565
for tool in params.dynamic_tools {
match dynamic_tool_to_responses_api_tool(tool) {
Ok(converted_tool) => {
match dynamic_tool_to_loadable_tool_spec(tool) {
Ok(loadable_tool) => {
let handler_name = ToolName::new(tool.namespace.clone(), tool.name.clone());
plan.push_spec(
ToolSpec::Function(converted_tool),
loadable_tool.into(),
/*supports_parallel_tool_calls*/ false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Coalesce duplicate dynamic namespace specs before registration

build_tool_registry_plan pushes each namespaced dynamic tool as its own ToolSpec::Namespace. Multiple tools in one namespace therefore emit repeated namespace wrappers, unlike MCP where entries are grouped first. This bloats tool payloads and can create ambiguous duplicate namespace declarations in model-visible specs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds true

}
}

impl From<LoadableToolSpec> for ToolSpec {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we using ToolSpec directly?

@pakrym-oai pakrym-oai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaner! Please check the namespace deduplication comment that codex left. Sounds plausible.

@sayan-oai sayan-oai merged commit dc1a8f2 into main Apr 21, 2026
25 checks passed
@sayan-oai sayan-oai deleted the codex/fix-deferred-dynamic-tool-routing branch April 21, 2026 06:13
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants