feat: dynamic tools injection (#9539)

## Summary
Add dynamic tool injection to thread startup in API v2, wire dynamic
tool calls through the app server to clients, and plumb responses back
into the model tool pipeline.

### Flow (high level)
- Thread start injects `dynamic_tools` into the model tool list for that
thread (validation is done here).
- When the model emits a tool call for one of those names, core raises a
`DynamicToolCallRequest` event.
- The app server forwards it to the client as `item/tool/call`, waits
for the client’s response, then submits a `DynamicToolResponse` back to
core.
- Core turns that into a `function_call_output` in the next model
request so the model can continue.

### What changed
- Added dynamic tool specs to v2 thread start params and protocol types;
introduced `item/tool/call` (request/response) for dynamic tool
execution.
- Core now registers dynamic tool specs at request time and routes those
calls via a new dynamic tool handler.
- App server validates tool names/schemas, forwards dynamic tool call
requests to clients, and publishes tool outputs back into the session.
- Integration tests
This commit is contained in:
jif-oai
2026-01-26 11:06:44 +01:00
committed by GitHub
parent 25fccc3d4d
commit d594693d1a
25 changed files with 864 additions and 49 deletions

View File

@@ -196,12 +196,21 @@ impl ThreadManager {
}
pub async fn start_thread(&self, config: Config) -> CodexResult<NewThread> {
self.start_thread_with_tools(config, Vec::new()).await
}
pub async fn start_thread_with_tools(
&self,
config: Config,
dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
) -> CodexResult<NewThread> {
self.state
.spawn_thread(
config,
InitialHistory::New,
Arc::clone(&self.state.auth_manager),
self.agent_control(),
dynamic_tools,
)
.await
}
@@ -224,7 +233,13 @@ impl ThreadManager {
auth_manager: Arc<AuthManager>,
) -> CodexResult<NewThread> {
self.state
.spawn_thread(config, initial_history, auth_manager, self.agent_control())
.spawn_thread(
config,
initial_history,
auth_manager,
self.agent_control(),
Vec::new(),
)
.await
}
@@ -262,6 +277,7 @@ impl ThreadManager {
history,
Arc::clone(&self.state.auth_manager),
self.agent_control(),
Vec::new(),
)
.await
}
@@ -330,6 +346,7 @@ impl ThreadManagerState {
Arc::clone(&self.auth_manager),
agent_control,
session_source,
Vec::new(),
)
.await
}
@@ -341,6 +358,7 @@ impl ThreadManagerState {
initial_history: InitialHistory,
auth_manager: Arc<AuthManager>,
agent_control: AgentControl,
dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
) -> CodexResult<NewThread> {
self.spawn_thread_with_source(
config,
@@ -348,6 +366,7 @@ impl ThreadManagerState {
auth_manager,
agent_control,
self.session_source.clone(),
dynamic_tools,
)
.await
}
@@ -359,6 +378,7 @@ impl ThreadManagerState {
auth_manager: Arc<AuthManager>,
agent_control: AgentControl,
session_source: SessionSource,
dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
) -> CodexResult<NewThread> {
let CodexSpawnOk {
codex, thread_id, ..
@@ -370,6 +390,7 @@ impl ThreadManagerState {
initial_history,
session_source,
agent_control,
dynamic_tools,
)
.await?;
self.finalize_thread_spawn(codex, thread_id).await