Make tool executor specs mandatory (#23870)

## Why

`ToolExecutor` is the runtime contract that keeps a callable tool and
its model-visible spec together. Leaving `spec()` optional lets a
registered runtime silently omit that half of the contract, and it also
overloads a missing spec as an exposure decision for tools that should
stay dispatchable without being shown to the model.

## What

- Make `ToolExecutor::spec()` required and update core, extension, and
test tool executors to return a concrete `ToolSpec`.
- Add `ToolExposure::Hidden` for dispatch-only tools. The legacy
`shell_command` runtime in unified-exec sessions now uses that explicit
exposure instead of hiding itself by omitting a spec.
- Build MCP tool specs when `McpHandler` is constructed so invalid MCP
specs are skipped before the handler is registered.
- Keep tool planning aligned with the new contract for direct, deferred,
hidden, code-mode, dynamic, and namespaced tool paths.

## Testing

- Added tool-plan coverage that invalid MCP tool specs are not
registered.
- Updated shell-family coverage for the hidden legacy `shell_command`
runtime and the affected tool executor test fixtures.
This commit is contained in:
jif-oai
2026-05-21 15:25:56 +02:00
committed by GitHub
parent 94442b7f95
commit 516f134641
50 changed files with 304 additions and 193 deletions
@@ -94,8 +94,8 @@ impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler {
ToolName::plain(PUBLIC_TOOL_NAME)
}
fn spec(&self) -> Option<ToolSpec> {
Some(self.spec.clone())
fn spec(&self) -> ToolSpec {
self.spec.clone()
}
async fn handle(
@@ -47,8 +47,8 @@ impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler {
ToolName::plain(WAIT_TOOL_NAME)
}
fn spec(&self) -> Option<ToolSpec> {
Some(create_wait_tool())
fn spec(&self) -> ToolSpec {
create_wait_tool()
}
async fn handle(