The capabilities
The descriptions are written for a model. They say when to reach for a tool rather than what the code does, because that sentence is the only thing the model reads before deciding.
SHOW_PAGE_TOOL is the sixth capability and is deliberately not built in. The built-in list is declared unconditionally, so a deployment with no renderer would be telling every model it can show a web page, promising the caller and then apologising on every call. An absent tool is honest; a broken one is not. A plugin that actually has a renderer registers it:
Declaring them
Every provider invented its own JSON for “here is a function you may call”, soschemas renders one list in the shape yours wants.
An unknown dialect falls back to
flat rather than raising. A tool a model never sees is a worse outcome than a shape one provider happens to also accept, and a crash at connect time helps nobody.
tool_schemas(dialect, extra) is the same rendering without a session, for a plugin that wants the declarations before there is a call to bind them to. CallTools.schemas is a thin wrapper over it.
BUILT_IN_TOOLS is the tuple your own specs are checked against, which is why a tool of your own may not shadow one and why that refusal happens at registration.
Running them
Translate your provider’s tool-call frame into a name and a dict, and hand it over:run instead:
ToolResult is from standin.calltools import ToolResult, not the standin barrel, because a caller reads its two attributes far more often than it names the type.
ok is false only when the SDK knows the tool did not do what it was asked: a missing or malformed argument, a rejected value, a handler that raised, or a name no tool answers. It is not a verdict on the vision tools, which answer in sentences by design, so “there is nothing to look at” comes back as ok with the reason in the text.
Tools of your own
Your own tools go in the same place, so the model sees one list:register raises ValueError when you try, at registration rather than at the first call, because a shadowed end_call is an agent that has quietly lost the ability to hang up, and that is not something to discover mid-conversation.
The provider plugins already do all of this. Reach for
CallTools when you are writing a plugin of your own, or adding capabilities to a handler you wrote yourself.Tools that are not built in
Five more capabilities ship asToolSpec values you register yourself, for the same reason as SHOW_PAGE_TOOL: each one depends on something the call may not have, and an agent told it can do something it cannot is worse than one that never mentions it.
All five are on the
standin barrel, and all five go through tools.register(spec, handler) exactly as your own do.
Whether to answer at all
A tool decides what an agent can do. In a meeting, the prior question is whether it should say anything, and the SDK ships that separately:GroupGate and the wake-phrase matching around it. It is inert on a one-to-one call, so a plugin that never configures a phrase behaves exactly as it did before. See Group calls.