AI SDK tools
The outbound tools as a tool set for the AI SDK, keyed by tool name and ready to pass to generateText or streamText.
aiSdkTools(outbound) from outbound-sdk/tools/ai-sdk returns the tools as one AI SDK tool per outbound tool, keyed by tool name. Pass the object as tools to generateText or streamText.
This subpath imports ai, which is an optional peer dependency: version 6 or 7. Install it only if you import this subpath. See installation.
aiSdkTools takes the same only and extra options as createTools.
Build the tools and run one
import { createOutbound } from "outbound-sdk";
import { demoData, memory } from "outbound-sdk/memory";
import { aiSdkTools } from "outbound-sdk/tools/ai-sdk";
const outbound = createOutbound({
adapter: memory({ data: demoData(), workspaceId: "acme" }),
});
// One AI SDK tool per outbound tool, keyed by tool name.
const tools = aiSdkTools(outbound, {
only: ["campaign_overview", "sender_health"],
});
console.log(Object.keys(tools));
const overview = tools.campaign_overview;
console.log(overview?.description);
// What the AI SDK does when the model calls the tool, done by hand here.
const result = await overview?.execute?.(
{ status: "paused" },
{ context: undefined, messages: [], toolCallId: "call_1" }
);
console.log(JSON.stringify(result, null, 2));
// A failure is a result the model can read, not an exception.
const failed = await overview?.execute?.(
{ client: "acme" },
{ context: undefined, messages: [], toolCallId: "call_2" }
);
console.log(JSON.stringify(failed));
[ 'campaign_overview', 'sender_health' ]
How every campaign is doing right now: sends, replies, positive replies, bounces and reply rate for each campaign with the given status. One vendor request per campaign. Rates are computed from counts; a null means the vendor does not report that count.
{
"data": {
"campaigns": [
{
"campaign": "memory:acme:cmp_agencies",
"channel": "email",
"name": "Agency owners",
"metrics": {
"bounce_rate": 0.0667,
"bounced": 4,
"connections_accepted": null,
"connections_sent": null,
"contacted": 60,
"opened": 18,
"positive": 0,
"positive_reply_rate": 0,
"replied": 1,
"reply_rate": 0.0167,
"reply_rate_basis": "contacted",
"sent": 60
},
"status": "paused"
}
]
},
"meta": {
"evidence": "exact",
"provider": "memory",
"requests": 1,
"today": "2026-11-14"
},
"next": "Pick another of the listed tools to go further.",
"ok": true
}
{"error":{"code":"Invalid","message":"campaign_overview: \"client\" is not an input. It takes: from, limit, status, to"},"next":"Check the input and try again.","ok":false}
Each tool has a description, an inputSchema built from the tool’s JSON Schema, and an execute function that returns the tool’s result object. A failed tool returns { ok: false } from execute. It does not throw, so the AI SDK passes the failure to the model as an ordinary tool result.
Wiring it to a model
import { generateText, stepCountIs } from "ai";
import type { LanguageModel } from "ai";
import type { OutboundApi } from "outbound-sdk";
import { aiSdkTools } from "outbound-sdk/tools/ai-sdk";
/**
* Asks a model a question about `outbound`. The model may call the tools,
* read what they return and answer, in up to five steps.
*/
export const ask = (
outbound: OutboundApi,
model: LanguageModel,
prompt: string
) =>
generateText({
model,
prompt,
stopWhen: stepCountIs(5),
tools: aiSdkTools(outbound),
});
ask takes the instance, a model and a question. stopWhen: stepCountIs(5) gives the model up to five steps, so it can call a tool, read the result and answer inside one generateText call.
A real run
This function was run against a real model on 2026-11-14: deepseek/deepseek-v4-flash, through the Vercel AI Gateway, with ai 7. The outbound side was the in-memory adapter’s demo data, so the run shows a model calling the tools and answering from what they return. It says nothing about a vendor. This is the transcript of that run: the question, each tool call the model made, and its answer.
{
"answer": "There is currently one active campaign:\n\n**Q4 SaaS founders** (`cmp_founders`, email channel)\n- Sends: 90\n- Replies: 7 (7.8% reply rate)\n\nNotes: 52 of the 90 sends were opened, 3 were positive, and 1 bounced. The account has no other campaigns in the active state right now.\n\nWant me to dig into this campaign at all — e.g., what's driving the replies, deliverability, or how many leads it has left?",
"model": "deepseek/deepseek-v4-flash",
"prompt": "Which campaigns are active, and how many sends and replies has each had?",
"ranAt": "2026-11-14",
"steps": 2,
"toolCalls": [
{
"input": {
"status": "active"
},
"tool": "campaign_overview"
}
],
"via": "Vercel AI Gateway"
}
The first live run found a problem that is now fixed. The model does not know today’s date, so it asked for 2024 to 2026 and found no data. Every tool result now carries meta.today, and the date inputs tell the model to leave the range out for the whole history and not to guess.
Reply text
A model given reply_samples can ask for reply text. It comes back truncated and marked as untrusted. See untrusted content.