Skip to main content
A voice model can talk. What it cannot do, unless you tell it, is hang up, put a picture on its own video tile, react with an expression, or look at what the caller is showing. Those are properties of being on a Microsoft Teams call, not of any provider, so they live in the SDK. Every plugin gets the same set, and a caller gets the same agent whichever provider answers.

The capabilities

display is "fullscreen" or "overlay", and it is a schema enum rather than a list written into the description, because a model obeys a schema enum far more reliably than the same choice spelled out in prose. Vision and the avatar covers which to reach for. 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 is deliberately not in that list. It needs a renderer, and a deployment with none would still 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 does have a renderer registers SHOW_PAGE_TOOL itself.

More tools the SDK ships

None of these are built in either, for the same reason: an agent with nothing behind a tool should not be told it has one. Each is a ToolSpec ready to register.

Declaring them

Every provider invented its own JSON for “here is a function you may call”, so schemas renders one list in the shape yours wants.
vision is the only option, and it is optional: leave it out and CallTools builds a VisionTools of its own. Pass one when you want to give it a describer, a renderer or a budget, which is nearly always, because a VisionTools with no describer can put pictures up but cannot say what it sees. The instance is on tools.vision afterwards either way. An unknown dialect falls back to flat rather than throwing. 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.

Running them

Translate your provider’s tool-call frame into a name and an object, and hand it over:
Dispatch never throws. It returns a sentence, because the result goes back to a model that will read it out. “I could not show that because the image was too large” is worth something to a model; a stack trace is not. When your provider’s tool-result frame also carries an error flag, use run instead:
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 threw, 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:
The handler may return a string or a promise of one, and what it returns is what the model is told. Keep it fast: the caller is listening to silence while it runs. A tool of your own may not shadow a built-in. That is refused when you register it 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.

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 at all, and the SDK ships that separately: GroupGate and the wake-phrase matching around it. It is inert on a 1:1 call, so a plugin that never configures a phrase behaves exactly as it did before. See Group calls.

Next

Vision and the avatar

What look, look_back and show_image actually do.

The avatar

What express puts on the face, and the lip-sync beside it.

Consulting

The two tools that hand real work to a second agent.

Call handler

The session every one of these tools is bound to.