The Vercel AI SDK's job is calling a model, streaming its output to a UI, and letting it invoke tools you define. Authenticating the end user calling your agent, authorizing what a given tool call is allowed to do, and capping how much a session can spend are decisions your application code has to make, at the API route or edge function that hosts the SDK call — that is the layer around the SDK, not something built into it.
This gap is easy to miss because the SDK is so commonly used to ship agent features fast: a chat UI, a streaming response, a tool that looks up an order or drafts an email, wired together in an afternoon. The speed is real. The governance has to be added separately.
What the AI SDK provides
The Vercel AI SDK is a TypeScript/JavaScript library for building AI-powered features into web applications. Its core value is provider abstraction and UI integration: the same code pattern for generating text or streaming a response works across multiple model providers, and the SDK ships hooks and components that make wiring a streaming chat interface into a React or similar frontend straightforward.
For agent behavior specifically, the SDK provides tool-calling primitives: you define a tool with a name, a description, a typed parameter schema, and an execute function, and pass a set of tools into a generation call. The model can choose to call one or more tools, the SDK executes the corresponding function, and the result is fed back into the model for further reasoning — a loop that can run multiple steps until the model produces a final answer, common in the "multi-step tool calling" pattern the SDK documents for building agent-like behavior.
All of this runs as ordinary application code. A tool's execute function is a function you wrote, running in whatever server context invoked it — typically an API route or a serverless/edge function in the same deployment as the rest of your web application.
Where the governance gap sits
1. Caller-to-agent identity is your route handler's job
Because the SDK is invoked from your own API route, the "identity" available to a tool call is whatever your route handler already has: a session cookie, a user record, an API key. Ask whether your route handler introduces a distinct identity for the agent itself, separate from the request that triggered it — the SDK doesn't do this for you. If your route handler does not explicitly attach the calling user's identity and permissions to the tool execution context, every tool call runs with whatever ambient access the route's own backend credentials hold — not the calling user's actual permissions. This is the same principal-confusion problem covered in why agents need their own credentials: the agent needs an identity distinct from the request that invoked it, not a borrowed one.
2. Tool execution shares the application's blast radius
Because a tool's execute function typically runs inside the same process or function as the rest of your backend logic, a tool that calls an internal service, a database, or a third-party API does so with whatever access that deployment already has configured — often the same service credentials used by non-agent code paths. There is no default isolation between "code that serves a normal API request" and "code that executes because a model decided to call a tool." A tool that queries a database using the application's general-purpose database credential, for instance, has that credential's full access, not a narrower one scoped to what the specific tool needs. This is the same tool-authorization gap described in least privilege for AI agents, and it applies with extra force here because the execution environment is the same one already trusted to run the rest of the app.
3. Multi-step tool loops have no external stop condition by default
The multi-step tool-calling pattern lets a model call a tool, receive the result, and decide to call another tool, repeating until it produces a final answer or a configured step limit is reached. A step limit bounds iteration count. Bounding cost, the sensitivity of the actions taken across those steps, and whether the pattern of calls looks anomalous compared to a normal session are separate controls the step limit alone doesn't provide. A model that loops through several expensive tool calls per turn, across many concurrent user sessions, can generate a cost pattern that a per-request step limit does nothing to catch. Anomaly detection for agent spend and budgets versus rate limits both apply directly here — the SDK's step limit is a loop-termination control, not a cost control.
4. Streaming responses complicate output-side controls
Because a defining feature of the SDK is streaming partial output to the UI as the model generates it, content that would otherwise be caught by a post-generation output filter may already have reached the browser before a full-response check completes. An output guardrail designed for a single, complete response needs to be re-thought for a token-by-token stream — either buffering and checking chunks before they render, or accepting that a mid-stream filter has a narrower window to act. This is a specific instance of the bidirectional control problem discussed in bidirectional guardrails: the control has to work with the actual delivery mechanism, not an idealized single response.
What good looks like
| Concern | AI SDK responsibility | Application responsibility |
|---|---|---|
| Model/provider integration | Unified call interface, streaming | — |
| Tool invocation | Parsing the model's tool-call intent, executing the function | Scoping what credentials the function has access to |
| Caller identity | — | Attaching a distinct, verified identity to every tool execution context |
| Spend | Step-count limit per generation | Per-user and per-org budget enforcement, anomaly alerts |
| Output filtering | Token streaming | Buffering or checking sensitive output before or as it streams |
A production deployment built on the AI SDK should be able to show, for any session: which authenticated identity triggered it, which tools executed with which scoped credentials (not the application's general-purpose ones), what the running cost was against a budget, and what content reached the UI and when. None of that is a configuration flag in the SDK — it is infrastructure your team builds around the API route that hosts it. Start by separating tool execution credentials from your application's default service credentials, then add identity attribution and spend limits at the same layer, before treating a fast SDK integration as a production-ready agent. For the broader control set this fits into, see the AI agent security guide and how to set budgets for AI agents.
Common questions
Does deploying on the edge runtime instead of a Node.js serverless function change the governance picture?
Not in any way that removes the gap. Edge runtimes typically impose more restrictions on what code can run and how long it can run, which incidentally bounds some worst cases, but neither runtime choice adds caller authentication, tool authorization, or spend tracking on its own. Whichever runtime you deploy to, the identity and authorization work still has to be added explicitly in your route handler before the model or tool call executes.
If the frontend never sees the tool's raw output, is that enough output control?
Not by itself. Withholding raw tool output from the client and only forwarding a model-generated summary reduces one specific leakage path, but an output guardrail is still needed on top of it — the model summarizing the tool result has already seen whatever sensitive content the tool returned, and a well-crafted prompt or a model error can still surface it in the summary. Treat this as a partial mitigation, not a substitute for filtering the tool result itself before it reaches the model.
Can I rely on the model provider's own safety filters instead of adding my own guardrails?
Provider-side content filters catch broad categories of harmful content, which is useful, but they are not aware of your application's specific authorization rules, your tool's specific side effects, or your organization's spend limits. They are a general-purpose layer underneath your application-specific one, not a replacement for it — the same distinction covered in AI guardrails versus an LLM firewall. A provider filter catching disallowed content and your own authorization layer catching an unauthorized action are two separate checks, and a production deployment needs both running, not one substituting for the other.