mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Represent dynamic tools with explicit namespaces internally (#27365)
Follow-up to #27356. ## Stack note This PR changes Codex's internal dynamic-tool shape while leaving `thread/start` unchanged. App-server therefore converts the existing per-tool input into explicit functions and namespaces before passing it to core. [#27371](https://github.com/openai/codex/pull/27371) updates `thread/start` to use the same explicit shape and removes this temporary conversion. ## Why Dynamic tools repeat namespace metadata on every function. Core should keep one explicit namespace with its member tools so descriptions and membership stay consistent across sessions and runtime planning. ## What changed - Represent dynamic tools as top-level functions or explicit namespaces in protocol and session state. - Read old flat rollout metadata and write the canonical hierarchy. - Flatten namespace members only when registering callable tools. - Keep `thread/start.dynamicTools` flat for now and normalize it at the app-server boundary. New builds can read old rollout metadata. Older builds cannot read newly written hierarchical metadata. ## Test plan - `just test -p codex-app-server thread_start_normalizes_legacy_dynamic_tools_into_model_request` - `just test -p codex-protocol session_meta_normalizes_legacy_dynamic_tools` - `just test -p codex-core resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled` - `just test -p codex-core tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call` - `just test -p codex-core code_mode_can_call_hidden_dynamic_tools` - `just test -p codex-tools`
This commit is contained in:
@@ -12,6 +12,9 @@ use codex_login::CodexAuth;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;
|
||||
use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;
|
||||
use codex_protocol::dynamic_tools::DynamicToolNamespaceSpec;
|
||||
use codex_protocol::dynamic_tools::DynamicToolNamespaceTool;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -3286,6 +3289,7 @@ text(JSON.stringify(tool));
|
||||
serde_json::json!({
|
||||
"name": "mcp__rmcp__echo",
|
||||
"description": concat!(
|
||||
"Use these tools to exercise the rmcp test server.\n\n",
|
||||
"Echo back the provided message and include environment data.\n\n",
|
||||
"exec tool declaration:\n",
|
||||
"```ts\n",
|
||||
@@ -3312,20 +3316,25 @@ async fn code_mode_can_call_hidden_dynamic_tools() -> Result<()> {
|
||||
.thread_manager
|
||||
.start_thread_with_tools(
|
||||
base_test.config.clone(),
|
||||
vec![DynamicToolSpec {
|
||||
namespace: Some("codex_app".to_string()),
|
||||
name: "hidden_dynamic_tool".to_string(),
|
||||
description: "A hidden dynamic tool.".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": { "type": "string" }
|
||||
},
|
||||
"required": ["city"],
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
defer_loading: true,
|
||||
}],
|
||||
vec![DynamicToolSpec::Namespace(DynamicToolNamespaceSpec {
|
||||
name: "codex_app".to_string(),
|
||||
description: "Codex app tools.".to_string(),
|
||||
tools: vec![DynamicToolNamespaceTool::Function(
|
||||
DynamicToolFunctionSpec {
|
||||
name: "hidden_dynamic_tool".to_string(),
|
||||
description: "A hidden dynamic tool.".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": { "type": "string" }
|
||||
},
|
||||
"required": ["city"],
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
defer_loading: true,
|
||||
},
|
||||
)],
|
||||
})],
|
||||
)
|
||||
.await?;
|
||||
let mut test = base_test;
|
||||
@@ -3333,8 +3342,8 @@ async fn code_mode_can_call_hidden_dynamic_tools() -> Result<()> {
|
||||
test.session_configured = new_thread.session_configured;
|
||||
|
||||
let code = r#"
|
||||
const tool = ALL_TOOLS.find(({ name }) => name === "codex_app_hidden_dynamic_tool");
|
||||
const out = await tools.codex_app_hidden_dynamic_tool({ city: "Paris" });
|
||||
const tool = ALL_TOOLS.find(({ name }) => name === "codex_app__hidden_dynamic_tool");
|
||||
const out = await tools.codex_app__hidden_dynamic_tool({ city: "Paris" });
|
||||
text(
|
||||
JSON.stringify({
|
||||
name: tool?.name ?? null,
|
||||
@@ -3441,7 +3450,7 @@ text(
|
||||
)?;
|
||||
assert_eq!(
|
||||
parsed.get("name"),
|
||||
Some(&Value::String("codex_app_hidden_dynamic_tool".to_string()))
|
||||
Some(&Value::String("codex_app__hidden_dynamic_tool".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
parsed.get("out"),
|
||||
@@ -3452,9 +3461,10 @@ text(
|
||||
.get("description")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|description| {
|
||||
description.contains("A hidden dynamic tool.")
|
||||
description.contains("Codex app tools.")
|
||||
&& description.contains("A hidden dynamic tool.")
|
||||
&& description.contains("declare const tools:")
|
||||
&& description.contains("codex_app_hidden_dynamic_tool(args:")
|
||||
&& description.contains("codex_app__hidden_dynamic_tool(args:")
|
||||
})
|
||||
);
|
||||
|
||||
@@ -3475,17 +3485,22 @@ async fn code_mode_excludes_configured_nested_tool_namespaces() -> Result<()> {
|
||||
.thread_manager
|
||||
.start_thread_with_tools(
|
||||
base_test.config.clone(),
|
||||
vec![DynamicToolSpec {
|
||||
namespace: Some("excluded".to_string()),
|
||||
name: "lookup".to_string(),
|
||||
description: "An excluded dynamic tool.".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
defer_loading: false,
|
||||
}],
|
||||
vec![DynamicToolSpec::Namespace(DynamicToolNamespaceSpec {
|
||||
name: "excluded".to_string(),
|
||||
description: "Excluded tools.".to_string(),
|
||||
tools: vec![DynamicToolNamespaceTool::Function(
|
||||
DynamicToolFunctionSpec {
|
||||
name: "lookup".to_string(),
|
||||
description: "An excluded dynamic tool.".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
defer_loading: false,
|
||||
},
|
||||
)],
|
||||
})],
|
||||
)
|
||||
.await?;
|
||||
let mut test = base_test;
|
||||
|
||||
Reference in New Issue
Block a user