---
title: "Vercel AI SDK"
description: "The Vercel AI SDK gives TypeScript one API for many model providers, with typed tools. The Dex adapter runs an AI SDK agent definition inside a durable session. It is available for TypeScript."
canonical_url: "https://docs.superdurable.io/agent-frameworks/vercel-ai-sdk/"
source: "docs/content/agent-frameworks/vercel-ai-sdk.mdx"
locale: "en"
---

# Vercel AI SDK

The Vercel AI SDK gives TypeScript one API for many model providers, with typed tools. The Dex adapter runs an AI SDK agent definition inside a durable session. It is available for TypeScript.

## When to use it

Use it when your TypeScript application already uses AI SDK models and tools. Keep the model, the tools, and **stopWhen**, and let Dex own the session.

## How a session runs

Every tool reaches the model without its **execute**, so the AI SDK returns the tool calls instead of running them. Each **RunModel** Step makes one model call with **streamText**.

Each tool call runs the tool's original **execute** in its own Step, in parallel: **CallTool** for a read or an idempotent write, **CallSingleAttemptTool** for any other write. A call that needs approval waits in **AwaitToolApproval** first. The next **RunModel** adds the recorded results, checks **stopWhen**, and calls the model again.

A turn that looks up an order runs **RunModel**, **CallTool**, **ResumeTurn**, and **RunModel**.

## Install

Install the adapter with its framework. Each adapter brings in the Dex Agents core.

**TypeScript**

```bash
npm i @superdurable/dex-agents-vercel-ai @superdurable/dex-agents @superdurable/dex ai @ai-sdk/openai zod
```

Example: [agents/typescript/vercel-ai/README.md](https://github.com/superdurable/dex/tree/main/agents/typescript/vercel-ai/README.md)

## The example

The example is the [order support agent](https://docs.superdurable.io/agent-frameworks.md#the-examples) that every framework page uses.

The Flow type is **VercelAiOrderSupportAgent**. The full example is under [examples/typescript/src/products/agent-harnesses](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses).

### Agent and tools

The definition holds the model, the instructions, AI SDK tools, and the tool policies. Dex decides approval from the policies.

**TypeScript**

```typescript
export function createVercelAiOrderSupportFlow(
  orderDesk: OrderDesk,
  model: LanguageModel,
  slack: Connection | undefined,
): AgentSessionFlow {
  return createVercelAiAgentSessionFlow(
    {
      model,
      instructions: orderSupportInstructions,
      tools: {
        look_up_order: tool({
          description: "Look up one order by its order ID and return its items and status.",
          inputSchema: z.object({ order_id: z.string() }),
          execute: ({ order_id }) => orderDesk.describeOrder(order_id),
        }),
        cancel_order: tool({
          description: "Cancel one order that has not shipped yet.",
          inputSchema: z.object({ order_id: z.string(), reason: z.string() }),
          execute: ({ order_id, reason }) => orderDesk.cancelOrder(order_id, reason),
        }),
      },
      toolPolicies: {
        look_up_order: { effect: "read" },
        cancel_order: { effect: "non_idempotent_write" },
      },
      toolSources: createSlackConnectorToolSources(slack),
    },
    vercelAiOrderSupportFlowType,
  );
}
```

Example: [examples/typescript/src/products/agent-harnesses/vercel-ai/vercel-ai-order-support-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/vercel-ai/vercel-ai-order-support-flow.ts)

### Worker

One Worker serves the session Flows of every harness in the example. Build the same session Flow in every process that starts sessions or calls their RPCs.

**TypeScript**

```typescript
// One Slack connection for the agent examples; without it they offer no Slack tools.
const slack = await openSlackConnectionFromProject();
// ...
// One order desk for the four agent harness examples; each harness registers its own session Flow type.
const orderDesk = new OrderDesk();
// ...
const vercelAiOrderSupportFlow = createVercelAiOrderSupportFlow(orderDesk, loadVercelAiOrderSupportModel(), slack);
```

Example: [examples/typescript/src/registry.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/registry.ts)

### Session routes

Every harness serves these same routes. [Session routes](https://docs.superdurable.io/agent-frameworks.md#session-routes) explains them and the session token.

**TypeScript**

```typescript
  router.post("/sessions", async (request, response) => {
    const initialMessage = optionalString(requireBody(request), "message");
    // The server picks the session ID, so a caller cannot name another session.
    const sessionId = `order-support-${randomUUID()}`;
    await startAgentSession(client, flow, sessionId, { initialMessage }, { timeoutMs: 30 * DAY_MS });
    response.json({ session_id: sessionId, session_token: issueSessionToken(sessionId) });
  });
  router.get("/sessions/:id/snapshot", async (request, response) => {
    response.json(convertAgentSnapshotToJson(await client.invokeRPC(flow.getAgentSnapshot, sessionIdOf(request))));
  });
  router.post("/sessions/:id/messages", async (request, response) => {
    const sessionId = sessionIdOf(request);
    const message = { text: requireString(requireBody(request), "text") };
    sendAccepted(response, await client.invokeRPC(flow.sendUserMessage, sessionId, message));
  });
  router.post("/sessions/:id/interrupts/:interruptId/approve", async (request, response) => {
    const sessionId = sessionIdOf(request);
    const approval = { interruptId: String(request.params.interruptId), approved: true, rejectionMessage: null };
    sendAccepted(response, await client.invokeRPC(flow.resolveInterrupt, sessionId, approval));
  });
```

Example: [examples/typescript/src/products/agent-harnesses/order-support/order-support-routes.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/order-support/order-support-routes.ts)

## Run it locally

### Start Dex and the example

In a first terminal, render the session graph, then start Dex. Dex Web reads the graph when it starts, to show the session and its approval controls.

**TypeScript**

```bash
cd examples/typescript
npm install
mkdir -p definitions
npx dex-agents-session-graph --flow-type VercelAiOrderSupportAgent \
  --permission order-support.operate --out definitions/vercel-ai-sdk.json
dexcli dev --flow-rendering-dir ./definitions
```

Example: [examples/typescript/src/products/agent-harnesses/README.md](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/README.md)

**dexcli dev** stays in the foreground. In a second terminal, start the example:

**TypeScript**

```bash
cd examples/typescript
OPENAI_API_KEY=... npm start
```

Example: [examples/typescript/src/products/agent-harnesses/README.md](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/README.md)

The app serves on **127.0.0.1:8080**. Dex Web is at **<http://127.0.0.1:8802>**.

The example has no demo model. It calls OpenAI through **@ai-sdk/openai**, so it needs **OPENAI_API_KEY**.

| Variable                  | Default        | Meaning                            |
| ------------------------- | -------------- | ---------------------------------- |
| **DEX_VERCEL_AI_MODEL**   | **gpt-5-mini** | The OpenAI model name.             |
| **OPENAI_API_KEY**        | none; required | The OpenAI API key of the Worker.  |
| **DEX_AGENT_SESSION_KEY** | a random key   | The key that signs session tokens. |

### Call the session

Start a session, ask the agent to cancel order 1002, and approve the call:

**TypeScript**

```bash
base=http://127.0.0.1:8080/products/agent-harnesses/vercel-ai
session=$(curl -s -X POST "$base/sessions" -H 'Content-Type: application/json' \
  -d '{"message": "Please cancel order 1002"}')
id=$(echo "$session" | jq -r .session_id)
token=$(echo "$session" | jq -r .session_token)
until interrupt=$(curl -s "$base/sessions/$id/snapshot" -H "X-Agent-Session-Token: $token" \
  | jq -re '.pending_interrupts[0].interrupt_id'); do sleep 1; done
curl -s -X POST "$base/sessions/$id/interrupts/$interrupt/approve" -H "X-Agent-Session-Token: $token"
```

Example: [examples/typescript/src/products/agent-harnesses/README.md](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/README.md)

The loop reads the snapshot until the agent asks to run **cancel_order**. That call waits for approval, then runs once. Read the snapshot again to see the agent's answer.

### Approve in Dex Web

Instead of the last command, you can approve the call in Dex Web. Follow the steps in [Approve in Dex Web](https://docs.superdurable.io/agent-frameworks.md#approve-in-dex-web).

<a id="connect-connectors"></a>

## Connect Connectors

The agent can call [Connector](https://docs.superdurable.io/concepts/connector.md) operations as tools, as [Connectors as tools](https://docs.superdurable.io/agent-frameworks.md#connectors-as-tools) describes. The example adds the Slack tool **slack_post_thread_reply**, which replies in a Slack thread.

<a id="offer-connector-tools"></a>

### Offer the tool

Install **@superdurable/connectors-sdk** and the Connector package, such as **@superdurable/connector-slack**. The install commands are in [Connector tools](https://docs.superdurable.io/reference/agents/connector-tools.md#install).

The Worker opens the Slack connection **support-slack** from the project configuration. **connectorTool** turns the operation into a tool, and **ConnectorToolSource** holds it. Without the connection, the agent gets no Slack tool.

**TypeScript**

```typescript
export async function openSlackConnectionFromProject(
  environment: NodeJS.ProcessEnv = process.env,
): Promise<Connection | undefined> {
  // ...
  const project = await loadFromEnvironment(environment);
  // ...
  return newProjectConnection(project, slackConnectionName);
}

// ...

export function createSlackConnectorToolSources(slack: Connection | undefined): ToolSource[] {
  if (slack === undefined) {
    return [];
  }
  return [
    new ConnectorToolSource([
      connectorTool(slack.operations.postThreadReply(), slack.reference, {
        manifest,
        inputValidator: PostThreadReplyInputSchema,
        // The manifest does not ask for approval; this agent asks before every post.
        requiresApproval: true,
      }),
    ]),
  ];
}
```

Example: [examples/typescript/src/shared/slack-connector-tools.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/shared/slack-connector-tools.ts)

The Worker opens the connection once, at startup, and every agent uses it:

**TypeScript**

```typescript
// One Slack connection for the agent examples; without it they offer no Slack tools.
const slack = await openSlackConnectionFromProject();
```

Example: [examples/typescript/src/registry.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/registry.ts)

The agent passes the tool source next to its own tools:

**TypeScript**

```typescript
  return createVercelAiAgentSessionFlow(
    {
      // ...
      toolSources: createSlackConnectorToolSources(slack),
    },
    vercelAiOrderSupportFlowType,
  );
```

Example: [examples/typescript/src/products/agent-harnesses/vercel-ai/vercel-ai-order-support-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/agent-harnesses/vercel-ai/vercel-ai-order-support-flow.ts)

<a id="connector-tool-calls"></a>

### How a call runs

Each call of **slack_post_thread_reply** runs once, in its own **CallSingleAttemptTool** Step, after a person approves it. [The Slack Connector tool](https://docs.superdurable.io/agent-frameworks.md#slack-connector-tool) explains why.

The adapter offers the Connector tools after the agent's tools, as AI SDK tools without **execute**. A call never runs inside the model call. AI SDK **needsApproval** does not apply to it; the source decides approval.

<a id="configure-connector-connection"></a>

### Configure the connection

[Connections](https://docs.superdurable.io/reference/agents/connector-tools.md#connector-tools-connection) explains where the Worker finds the **support-slack** connection and what the Slack app needs.

To try it, start the example with **DEX_CONNECTOR_CONFIG_FILE** set. Then ask the agent to reply in a Slack thread, with the channel ID and the thread timestamp:

```text
Reply in Slack thread C0123456789 1710000000.000100: Order 1002 ships on Thursday.
```

The post waits for approval, like **cancel_order**.

## Limits

- A crash in **RunModel** reruns that model call and any provider-executed tools in it.
- A tool's **execute** gets **toolCallId** and **abortSignal**. Its **messages** are empty and its **context** is **undefined**.
- Approval belongs to Dex. AI SDK **toolApproval** is not exposed, and a **needsApproval** function is rejected. Provider-executed tools never wait for approval.
- Every tool runs in Dex, so a tool without **execute**, such as a browser-side tool, is rejected.
- Tool inputs and outputs must survive JSON.
- **prepareStep**, **activeTools**, **output**, and stream transforms are not exposed.

The adapter README lists every limit. See [Limits for long sessions](https://docs.superdurable.io/reference/agents/session-limits.md) for the server limits.

## Related

- [Agent Frameworks](https://docs.superdurable.io/agent-frameworks.md)
- [Agents reference](https://docs.superdurable.io/reference/agents.md)
- [TypeScript adapter README](https://github.com/superdurable/dex/tree/main/agents/typescript/vercel-ai)
