Dynamic tool calls - allow dynamic registration

Add experimental app-server support for connection-owned dynamic tool providers over both `stdio` and websocket.

Clients can now:
- register a tool with `toolProvider/register`
- unregister it with `toolProvider/unregister`

Registered tools are stored in an app-server registry, mapped back to the owning connection, and routed through the existing dynamic tool call flow. Tools with `injectIntoContext: true` are exposed to the model; tools with `injectIntoContext: false` remain callable only from `js_repl` via `codex.tool(...)`.

This also extends dynamic tool call events with `thread_id` so provider-owned tools can be invoked from newly created or resumed threads.

```mermaid
sequenceDiagram
    participant Client as Provider Client
    participant AppServer as App Server
    participant ThreadClient as Thread Client
    participant Core as Core Thread

    Client->>AppServer: toolProvider/register(tool)
    AppServer->>AppServer: store tool -> owning connection

    ThreadClient->>AppServer: thread/start
    AppServer->>Core: load thread + sync registered tools

    ThreadClient->>AppServer: turn/start
    AppServer->>Core: submit user input
    Core-->>AppServer: DynamicToolCallRequest(thread_id, tool, args, provider_owned=true)

    AppServer->>Client: item/tool/call
    Client-->>AppServer: DynamicToolCallResponse
    AppServer->>Core: submit dynamic tool response
    Core-->>AppServer: continue turn
This commit is contained in:
Channing Conger
2026-03-11 13:09:17 -07:00
parent f5bb338fdb
commit ee6257ae1c
35 changed files with 1157 additions and 30 deletions
+11
View File
@@ -10,15 +10,22 @@ pub struct DynamicToolSpec {
pub name: String,
pub description: String,
pub input_schema: JsonValue,
#[serde(default = "default_inject_into_context")]
pub inject_into_context: bool,
#[serde(default)]
pub provider_owned: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct DynamicToolCallRequest {
pub thread_id: String,
pub call_id: String,
pub turn_id: String,
pub tool: String,
pub arguments: JsonValue,
#[serde(default)]
pub provider_owned: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
@@ -37,3 +44,7 @@ pub enum DynamicToolCallOutputContentItem {
#[serde(rename_all = "camelCase")]
InputImage { image_url: String },
}
fn default_inject_into_context() -> bool {
true
}