## 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
58 lines
1.8 KiB
Rust
58 lines
1.8 KiB
Rust
use codex_app_server_protocol::DynamicToolCallResponse;
|
|
use codex_core::CodexThread;
|
|
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
|
|
use codex_protocol::protocol::Op;
|
|
use std::sync::Arc;
|
|
use tokio::sync::oneshot;
|
|
use tracing::error;
|
|
|
|
pub(crate) async fn on_call_response(
|
|
call_id: String,
|
|
receiver: oneshot::Receiver<serde_json::Value>,
|
|
conversation: Arc<CodexThread>,
|
|
) {
|
|
let response = receiver.await;
|
|
let value = match response {
|
|
Ok(value) => value,
|
|
Err(err) => {
|
|
error!("request failed: {err:?}");
|
|
let fallback = CoreDynamicToolResponse {
|
|
call_id: call_id.clone(),
|
|
output: "dynamic tool request failed".to_string(),
|
|
success: false,
|
|
};
|
|
if let Err(err) = conversation
|
|
.submit(Op::DynamicToolResponse {
|
|
id: call_id.clone(),
|
|
response: fallback,
|
|
})
|
|
.await
|
|
{
|
|
error!("failed to submit DynamicToolResponse: {err}");
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
|
|
let response = serde_json::from_value::<DynamicToolCallResponse>(value).unwrap_or_else(|err| {
|
|
error!("failed to deserialize DynamicToolCallResponse: {err}");
|
|
DynamicToolCallResponse {
|
|
output: "dynamic tool response was invalid".to_string(),
|
|
success: false,
|
|
}
|
|
});
|
|
let response = CoreDynamicToolResponse {
|
|
call_id: call_id.clone(),
|
|
output: response.output,
|
|
success: response.success,
|
|
};
|
|
if let Err(err) = conversation
|
|
.submit(Op::DynamicToolResponse {
|
|
id: call_id,
|
|
response,
|
|
})
|
|
.await
|
|
{
|
|
error!("failed to submit DynamicToolResponse: {err}");
|
|
}
|
|
}
|