Provider-executed tools
Web search, code execution and a gateway's own tools, run by the model provider inside the turn.
tool() declares code your application runs when Cereal calls it. providerTool() declares a capability
the model provider runs inside the turn, so there is no handler, nothing to serve,
and nothing for sync to register.
The argument is the provider's own tool object, verbatim. Cereal sends it as
written and checks only that it is an object with a non-empty type.
import { defineAgent, providerTool } from "@trycereal/sdk"
export default defineAgent({
agent: {
slug: "researcher",
model: { provider: "gateway", model: "anthropic/claude-opus-5" },
system: "Answer with current information and cite what you used.",
tools: [
providerTool({
type: "web_search_20250305",
name: "web_search",
max_uses: 5,
allowed_domains: ["arxiv.org"]
}),
providerTool(
{ type: "code_execution_20250825", name: "code_execution" },
{ betas: ["code-execution-2025-08-25"] }
)
]
}
})Use the provider's documented tool fields and a model that supports them. Cereal passes the tool object through; invalid provider-specific fields can fail on the first model request even if the agent definition was accepted.
The four options beside the tool
| Option | What it does |
|---|---|
name | What the transcript, the trace and the usage counter call it. Defaults to the tool object's name, else its type. |
provider | Pins the entry to one provider type. Unpinned, it is declared on every rung of the fallback ladder. |
betas | anthropic-beta values the tool needs. Anthropic only, and the one thing pass-through would otherwise lose. |
required | false lets a rung that cannot carry the tool run without it, recording a session.error. Defaults to true. |
A cross-provider fallback ladder writes one pinned entry per provider, because
Anthropic's web_search_20250305 is not OpenAI's web_search:
tools: [
providerTool({ type: "web_search_20250305", name: "web_search" }, { provider: "anthropic" }),
providerTool({ type: "web_search" }, { provider: "openai" })
]When the provider says no
Cereal refuses two things at write time and nothing else: a tool object with no type,
and a log name that collides with a tool Cereal itself runs. Everything else is the
provider's answer, and it arrives in the transcript as a session.error naming the
entry, so a rejected declaration does not read as a bad prompt.
Three shapes of that, all of them lines in the transcript rather than failures you have to go looking for:
- The provider rejected the request, and the error names the tools this request declared.
- The provider returned a result Cereal's model adapter cannot read, which happens for a genuinely new tool family rather than a new dated version of an existing one.
- The tool ran and this build cannot replay its blocks into the next prompt, so the model will not see those results on the next step. Said once per session.
What you see on the stream
- A
tool.call.started/.completedpair carryingexecutedBy: "provider", under the provider's own tool name. The call and its result arrive together, because the provider already ran it. - The model's citations on
agent.message.sources. - What the provider billed beyond tokens, in
model.request.completed.usage.serverToolRequests. Consult the provider for current tool pricing.
Provider tools bypass Cereal approvals
policies.toolCalls is evaluated over the calls the model asks Cereal to run. A
provider-executed call is already finished when it arrives, before any hook could have
been asked, so it can never become a tool.call.pending. A capability the provider
runs cannot be approved through Cereal's tool-call policy.
If a provider tool connects to an MCP server, that provider sends the requests. Cereal does not sign them or apply its approval policy. To have Cereal call the server with those controls, use an MCP tool declaration.
Some vendor tools expect *you* to run them
Anthropic's memory, bash, text_editor and computer, and OpenAI's shell and
apply_patch, are client-executed: the vendor supplies the schema and the caller runs
the tool. Declaring one as a provider-executed tool does not supply an implementation. Use a
custom tool with a handler for code that your application must run.
Long turns
Anthropic can pause a turn mid-flight while a server-side tool runs. Cereal continues the paused request with the same provider. Model fallback is disabled during that continuation, and the provider's code-execution container is reused across steps.
Compatibility
The earlier string form, such as providerTool("web_search", { maxUses: 5 }), remains
supported for its existing tool types. Use a provider tool object for new integrations.