Vercel AI SDK integration

View as Markdown

@getdial/ai-sdk packages Dial’s operations as Vercel AI SDK tools, so a model can send messages and place calls as part of its reasoning. It’s the TypeScript analog of the LangChain integration: a thin adapter over @getdial/sdk — it adds nothing to the REST contract, it just shapes Dial’s operations into AI SDK tool() definitions.

Not Vercel-only. The “Vercel AI SDK” is the open-source ai package — it runs on any JS host (Next.js on any platform, Node, Hono, Cloudflare Workers, Deno, Bun) and with any tool-calling model (Claude, GPT, Gemini, …). These tools work wherever it does.

Install

$npm install @getdial/ai-sdk ai zod

ai and zod are peer dependencies — your app already has them. The package pulls in @getdial/sdk as its Dial client.

Give the tools to a model

The quickest way is dialTools — construct it once with your API key and spread it into the tools field. To switch models, change one line; the tools don’t change.

1import { streamText } from "ai";
2import { anthropic } from "@ai-sdk/anthropic";
3import { dialTools } from "@getdial/ai-sdk";
4
5const result = streamText({
6 model: anthropic("claude-opus-4-8"), // or openai("gpt-5"), google("gemini-2.5-pro"), …
7 tools: dialTools({
8 apiKey: process.env.DIAL_API_KEY!,
9 fromNumberId: process.env.DIAL_FROM_NUMBER_ID, // optional default "from"
10 }),
11 messages,
12});

When fromNumberId is set, it’s the default for sendMessage and makeCall — the model can omit it, mirroring the CLI’s saved default from_number_id.

Available tools

ToolAction
listNumbersList your phone numbers
purchaseNumberProvision a new number (billable)
setNumberPropertiesUpdate a number’s nickname / inbound instruction / voice / duration cap
sendMessageSend an SMS (optionally MMS)
listMessagesList recent messages
makeCallPlace an AI voice call
listCallsList recent calls
getCallFetch one call by id
getBillingWallet balance, subscription, per-number mode
waitForMessageBlock until the next inbound SMS arrives, or time out

Each execute returns the structured Dial object (a Message, Call, etc.) — the AI SDK serializes the tool result back into the model for you.

Or pick individual tools

When you only want a subset, import the factories directly:

1import { sendMessageTool, makeCallTool } from "@getdial/ai-sdk";
2
3const tools = {
4 sendMessage: sendMessageTool({ apiKey: process.env.DIAL_API_KEY!, fromNumberId: "pn_..." }),
5 makeCall: makeCallTool({ apiKey: process.env.DIAL_API_KEY!, fromNumberId: "pn_..." }),
6};

Example: a Next.js chat route

A typical AI SDK app exposes a route handler that streams the model’s response. Drop dialTools(...) into it and the chat can text and call people:

app/api/chat/route.ts
1import { streamText, convertToModelMessages, type UIMessage } from "ai";
2import { anthropic } from "@ai-sdk/anthropic";
3import { dialTools } from "@getdial/ai-sdk";
4
5export async function POST(req: Request) {
6 const { messages }: { messages: UIMessage[] } = await req.json();
7
8 const result = streamText({
9 model: anthropic("claude-opus-4-8"),
10 tools: dialTools({
11 apiKey: process.env.DIAL_API_KEY!,
12 fromNumberId: process.env.DIAL_FROM_NUMBER_ID,
13 }),
14 messages: convertToModelMessages(messages),
15 });
16
17 return result.toUIMessageStreamResponse();
18}

Now an end user typing “text the plumber at +14155550123 that I’ll be 10 minutes late” gets the model to call sendMessage with the right to and body.

Send a code and await the reply

waitForMessage pairs with sendMessage so the model can verify a number inside a single turn — send a one-time code, then block on the inbound reply:

1import { generateText } from "ai";
2import { dialTools } from "@getdial/ai-sdk";
3
4const { text } = await generateText({
5 model,
6 tools: dialTools({ apiKey: process.env.DIAL_API_KEY!, fromNumberId: "pn_..." }),
7 // The model calls sendMessage to send the code, then waitForMessage to read the reply.
8 prompt: "Text +14155550123 the verification code 4821, wait up to 60s for their reply, and tell me whether it matches.",
9});

waitForMessage opens the account event stream, returns the first matching message.received, and times out after timeoutSeconds (default 30).

sendMessage is a write action and isn’t idempotent — a re-invoke after a failure can send a duplicate. makeCall accepts an idempotencyKey: a re-invoke with the same key returns the already-placed call instead of dialing again. See Retries and idempotency.

Receiving events beyond a single turn

waitForMessage is a one-shot wait, backed by a presence-based stream (not at-least-once — missed events replay only on reconnect within ~2 minutes). To react to inbound SMS or completed calls durably and continuously, don’t model it as a tool: use the Node SDK’s newEventsConnection() directly, or register a webhook (signed and retried at-least-once), and feed events into your agent however suits your app.