mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
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:
@@ -501,6 +501,10 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"injectIntoContext": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputSchema": true,
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -2795,6 +2799,33 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ToolProviderToolSpec": {
|
||||
"properties": {
|
||||
"defaultTimeoutMs": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"injectIntoContext": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputSchema": true,
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"defaultTimeoutMs",
|
||||
"description",
|
||||
"injectIntoContext",
|
||||
"inputSchema",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnInterruptParams": {
|
||||
"properties": {
|
||||
"threadId": {
|
||||
|
||||
@@ -1975,6 +1975,13 @@
|
||||
"callId": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerOwned": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tool": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1992,6 +1999,7 @@
|
||||
"required": [
|
||||
"arguments",
|
||||
"callId",
|
||||
"threadId",
|
||||
"tool",
|
||||
"turnId",
|
||||
"type"
|
||||
@@ -8112,6 +8120,13 @@
|
||||
"callId": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerOwned": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tool": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8129,6 +8144,7 @@
|
||||
"required": [
|
||||
"arguments",
|
||||
"callId",
|
||||
"threadId",
|
||||
"tool",
|
||||
"turnId",
|
||||
"type"
|
||||
|
||||
@@ -3338,6 +3338,13 @@
|
||||
"callId": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerOwned": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tool": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3355,6 +3362,7 @@
|
||||
"required": [
|
||||
"arguments",
|
||||
"callId",
|
||||
"threadId",
|
||||
"tool",
|
||||
"turnId",
|
||||
"type"
|
||||
@@ -11032,6 +11040,10 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"injectIntoContext": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputSchema": true,
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -16997,6 +17009,33 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ToolProviderToolSpec": {
|
||||
"properties": {
|
||||
"defaultTimeoutMs": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"injectIntoContext": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputSchema": true,
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"defaultTimeoutMs",
|
||||
"description",
|
||||
"injectIntoContext",
|
||||
"inputSchema",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ToolsV2": {
|
||||
"properties": {
|
||||
"view_image": {
|
||||
|
||||
@@ -3671,6 +3671,10 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"injectIntoContext": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputSchema": true,
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -5140,6 +5144,13 @@
|
||||
"callId": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerOwned": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tool": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5157,6 +5168,7 @@
|
||||
"required": [
|
||||
"arguments",
|
||||
"callId",
|
||||
"threadId",
|
||||
"tool",
|
||||
"turnId",
|
||||
"type"
|
||||
@@ -14818,6 +14830,33 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ToolProviderToolSpec": {
|
||||
"properties": {
|
||||
"defaultTimeoutMs": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"injectIntoContext": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputSchema": true,
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"defaultTimeoutMs",
|
||||
"description",
|
||||
"injectIntoContext",
|
||||
"inputSchema",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ToolsV2": {
|
||||
"properties": {
|
||||
"view_image": {
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"injectIntoContext": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"inputSchema": true,
|
||||
"name": {
|
||||
"type": "string"
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "./serde_json/JsonValue";
|
||||
|
||||
export type DynamicToolCallRequest = { callId: string, turnId: string, tool: string, arguments: JsonValue, };
|
||||
export type DynamicToolCallRequest = { threadId: string, callId: string, turnId: string, tool: string, arguments: JsonValue, providerOwned: boolean, };
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "../serde_json/JsonValue";
|
||||
|
||||
export type DynamicToolSpec = { name: string, description: string, inputSchema: JsonValue, };
|
||||
export type DynamicToolSpec = { name: string, description: string, inputSchema: JsonValue, injectIntoContext: boolean, };
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "../serde_json/JsonValue";
|
||||
|
||||
export type ToolProviderToolSpec = { name: string, description: string, inputSchema: JsonValue, defaultTimeoutMs: bigint, injectIntoContext: boolean, };
|
||||
@@ -278,6 +278,7 @@ export type { ThreadUnsubscribeParams } from "./ThreadUnsubscribeParams";
|
||||
export type { ThreadUnsubscribeResponse } from "./ThreadUnsubscribeResponse";
|
||||
export type { ThreadUnsubscribeStatus } from "./ThreadUnsubscribeStatus";
|
||||
export type { TokenUsageBreakdown } from "./TokenUsageBreakdown";
|
||||
export type { ToolProviderToolSpec } from "./ToolProviderToolSpec";
|
||||
export type { ToolRequestUserInputAnswer } from "./ToolRequestUserInputAnswer";
|
||||
export type { ToolRequestUserInputOption } from "./ToolRequestUserInputOption";
|
||||
export type { ToolRequestUserInputParams } from "./ToolRequestUserInputParams";
|
||||
|
||||
@@ -320,6 +320,16 @@ client_request_definitions! {
|
||||
params: v2::PluginUninstallParams,
|
||||
response: v2::PluginUninstallResponse,
|
||||
},
|
||||
#[experimental("toolProvider/register")]
|
||||
ToolProviderRegister => "toolProvider/register" {
|
||||
params: v2::ToolProviderRegisterParams,
|
||||
response: v2::ToolProviderRegisterResponse,
|
||||
},
|
||||
#[experimental("toolProvider/unregister")]
|
||||
ToolProviderUnregister => "toolProvider/unregister" {
|
||||
params: v2::ToolProviderUnregisterParams,
|
||||
response: v2::ToolProviderUnregisterResponse,
|
||||
},
|
||||
TurnStart => "turn/start" {
|
||||
params: v2::TurnStartParams,
|
||||
inspect_params: true,
|
||||
|
||||
@@ -1739,10 +1739,12 @@ mod tests {
|
||||
}),
|
||||
EventMsg::DynamicToolCallRequest(
|
||||
codex_protocol::dynamic_tools::DynamicToolCallRequest {
|
||||
thread_id: "thread-1".into(),
|
||||
call_id: "dyn-1".into(),
|
||||
turn_id: "turn-1".into(),
|
||||
tool: "lookup_ticket".into(),
|
||||
arguments: serde_json::json!({"id":"ABC-123"}),
|
||||
provider_owned: false,
|
||||
},
|
||||
),
|
||||
EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
|
||||
|
||||
@@ -509,6 +509,47 @@ pub struct DynamicToolSpec {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub input_schema: JsonValue,
|
||||
#[serde(default = "default_dynamic_tool_inject_into_context")]
|
||||
pub inject_into_context: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ToolProviderToolSpec {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub input_schema: JsonValue,
|
||||
pub default_timeout_ms: u64,
|
||||
pub inject_into_context: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ToolProviderRegisterParams {
|
||||
pub tool: ToolProviderToolSpec,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ToolProviderRegisterResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ToolProviderUnregisterParams {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ToolProviderUnregisterResponse {}
|
||||
|
||||
fn default_dynamic_tool_inject_into_context() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
|
||||
Reference in New Issue
Block a user