Expose explicit dynamic tool namespaces in thread start (#27371)

Stacked on #27365.

## Stack note

[#27365](https://github.com/openai/codex/pull/27365) kept `thread/start`
unchanged and converted its input in `thread_processor`. This PR updates
`thread/start` to accept explicit functions and namespaces directly.

Legacy per-tool arrays are still accepted and converted while reading
the request. As a result, `thread_processor` can validate and pass the
tools through directly, which is why some code added in #27365 is
removed here.

## Why

`thread/start.dynamicTools` still repeats namespace data on each
function even though core now stores explicit namespace groups. The
request API should use the same shape so each namespace has one
description and one member list.

## What changed

- Accept top-level functions and explicit namespace objects in
`dynamicTools`.
- Continue accepting fully legacy flat arrays, including
`exposeToContext`.
- Reject arrays that mix legacy and canonical entries.
- Reuse the protocol types directly and remove the temporary app-server
adapter.
- Update validation, docs, the test client, and generated schemas.

## Test plan

- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server
dynamic_tool_call_round_trip_sends_text_content_items_to_model`
- `just test -p codex-app-server
thread_start_normalizes_legacy_dynamic_tools_into_model_request`
- `just test -p codex-app-server
thread_start_rejects_mixed_dynamic_tool_formats`
- `just test -p codex-app-server
thread_start_rejects_hidden_dynamic_tools_without_namespace`
This commit is contained in:
sayan-oai authored and GitHub committed 2026-06-15 15:35:57 +00:00
1 parent 42ad752f36
commit 11faf9af94
19 files changed
+840 -525

No files matched your search

+24 -14
View File
@@ -271,16 +271,24 @@ Start a fresh thread when you need a new Codex conversation.
// Experimental: requires opt-in
"dynamicTools": [
{
"name": "lookup_ticket",
"description": "Fetch a ticket by id",
"deferLoading": true,
"inputSchema": {
"type": "object",
"properties": {
"id": { "type": "string" }
},
"required": ["id"]
}
"type": "namespace",
"name": "tickets",
"description": "Ticket management tools",
"tools": [
{
"type": "function",
"name": "lookup_ticket",
"description": "Fetch a ticket by id",
"deferLoading": true,
"inputSchema": {
"type": "object",
"properties": {
"id": { "type": "string" }
},
"required": ["id"]
}
}
]
}
],
} }
@@ -1502,13 +1510,14 @@ If the session approval policy uses `Granular` with `request_permissions: false`
`dynamicTools` on `thread/start` and the corresponding `item/tool/call` request/response flow are experimental APIs. To enable them, set `initialize.params.capabilities.experimentalApi = true`.
Dynamic tool identifiers follow the same constraints as Responses function tools:
Each entry in `dynamicTools` is either a top-level function or a namespace containing function tools. Dynamic tool identifiers follow the same constraints as Responses tools:
- `name` must match `^[a-zA-Z0-9_-]+$` and be between 1 and 128 characters.
- `namespace`, when present, must match `^[a-zA-Z0-9_-]+$` and be between 1 and 64 characters.
- `namespace` must not collide with reserved Responses runtime namespaces such as `functions`, `multi_tool_use`, `file_search`, `web`, `browser`, `image_gen`, `computer`, `container`, `terminal`, `python`, `python_user_visible`, `api_tool`, `tool_search`, or `submodel_delegator`.
- Namespace names must match `^[a-zA-Z0-9_-]+$` and be between 1 and 64 characters.
- Namespace descriptions must be at most 1,024 characters.
- Namespace names must not collide with reserved Responses runtime namespaces such as `functions`, `multi_tool_use`, `file_search`, `web`, `browser`, `image_gen`, `computer`, `container`, `terminal`, `python`, `python_user_visible`, `api_tool`, `tool_search`, or `submodel_delegator`.
Each dynamic tool may set `deferLoading`. When omitted, it defaults to `false`. Set it to `true` to keep the tool registered and callable by runtime features such as `code_mode`, while excluding it from the model-facing tool list sent on ordinary turns. When `tool_search` is available, deferred dynamic tools are searchable and can be exposed by a matching search result.
Each function may set `deferLoading`. When omitted, it defaults to `false`. Deferred functions must belong to a namespace. Set it to `true` to keep the function registered and callable by runtime features such as `code_mode`, while excluding it from the model-facing tool list sent on ordinary turns. When `tool_search` is available, deferred dynamic tools are searchable and can be exposed by a matching search result.
When a dynamic tool is invoked during a turn, the server sends an `item/tool/call` JSON-RPC request to the client:
@@ -1520,6 +1529,7 @@ When a dynamic tool is invoked during a turn, the server sends an `item/tool/cal
"threadId": "thr_123",
"turnId": "turn_123",
"callId": "call_123",
"namespace": "tickets",
"tool": "lookup_ticket",
"arguments": { "id": "ABC-123" }
}