Skip to main content
@novu/framework/ai-sdk lets you write agent handlers with the Vercel AI SDK. Return an AI SDK result straight from onMessage and Novu delivers the text — including tool loops and approval gating — to the conversation.

Before you start

Completed the Quickstart? Your agent, bridge, and project are already set up — jump to Minimal agent. Otherwise start with the Quickstart first. All three onboarding paths (dashboard, CLI, AI assistant) walk you through creating the agent and wiring the bridge. This page covers only what’s specific to the AI SDK adapter.

Prerequisites

Install the framework, the AI SDK, and a model provider:
npm install @novu/framework ai @ai-sdk/openai
npm install @novu/framework ai @ai-sdk/anthropic
npm install @novu/framework ai @ai-sdk/google
npm install @novu/framework ai @ai-sdk/groq
@novu/framework and ai are required; pick one provider package. Examples on this page use OpenAI — see the AI SDK providers list for others.

Minimal agent

Import agent and toModelMessages from @novu/framework/ai-sdk, then return generateText() from onMessage:
import { agent, toModelMessages } from '@novu/framework/ai-sdk';
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

export const supportAgent = agent('support-bot', {
  onMessage: async (message, ctx) =>
    generateText({
      model: openai('gpt-4o'),
      instructions: 'You are a helpful support agent.',
      messages: toModelMessages(ctx.history),
    }),
});
Make sure the agent id ('support-bot') matches the Identifier in your dashboard, and that this handler is registered on your bridge route — see Connecting your app. For handlers, replies, signals, typing, and tool approval, see Building blocks. The sections below cover what’s specific to the AI SDK adapter.

Returning from onMessage

On the AI SDK path, onMessage (and onToolApproval) can return an AI SDK result in addition to the usual reply types:
ReturnBehavior
generateText(...)Novu delivers the generated text
streamText(...)Novu waits for the full response, then delivers the final text (same outcome as generateText)
string / JSX CardDelivered as a normal reply — see Reply
nothingAfter you call ctx.reply() yourself
Returning streamText(...) behaves the same as returning generateText(...) — Novu waits for the full response, then posts the completed text.
To stream live updates in the thread, call streamText() inside the handler without returning it. Post a reply, then update it as chunks arrive with ReplyHandle.edit():
onMessage: async (message, ctx) => {
  const result = streamText({
    model: openai('gpt-4o'),
    messages: toModelMessages(ctx.history),
  });

  const msg = await ctx.reply('');
  let text = '';

  for await (const chunk of result.textStream) {
    text += chunk;
    await msg.edit(text);
  }
},
This pattern requires the provider to support message edits. See Agents and providers for capability details. Shorthand when you only need onMessage: pass the handler directly.
const supportAgent = agent('support-bot', async (message, ctx) =>
  generateText({ model: openai('gpt-4o'), messages: toModelMessages(ctx.history) })
);

toModelMessages()

Convert ctx.history into AI SDK ModelMessage[]:
messages: toModelMessages(ctx.history);
  • ctx.history already includes the current inbound message — do not append the message argument on top of it.
  • Tool approval cycles in history are mapped automatically, so auto-resume (below) works.

Automatic tool approval

Set needsApproval: true on an AI SDK tool(). When the model calls it, Novu posts the Approve / Deny card and pauses the turn. After the user decides, Novu re-runs onMessagetoModelMessages(ctx.history) now includes the decision, so the tool loop continues with no extra code from you.
import { tool } from 'ai';
import { z } from 'zod';

const issueRefund = tool({
  description: 'Issue a refund for an order',
  inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
  needsApproval: true,
  execute: async ({ orderId, amount }) => ({ orderId, amount, status: 'refunded' }),
});
Add onToolApproval when you need a hook after the click. See Tool approval for the full API.

Turn failures and onError

When a returned generateText() / streamText() result rejects — for example the provider errors or stream consumption fails — the failure propagates to the same dispatch boundary as a thrown handler error. Register onError on the agent to log, suppress user notification, send custom copy, or fall back to Novu’s generic message.
export const supportAgent = agent('support-bot', {
  onMessage: async (message, ctx) =>
    generateText({ model: openai('gpt-4o'), messages: toModelMessages(ctx.history) }),

  onError: async (error, ctx) => {
    console.error(error.message, error.cause);
    // return nothing → generic user copy from Novu
    // return { suppress: true } → silent
    // return 'Custom apology' → your copy
  },
});
Recoverable tool-error parts inside a multi-step AI SDK flow do not end the turn by themselves — only failures that reject the result or throw from your handler trigger onError. See Handlers and context — onError for the full pipeline.

MCP tools

Self-hosted agents do not use the dashboard MCP server configuration — that OAuth flow and token vault are for managed agents only. On the custom code path, you wire external MCP servers yourself with the AI SDK MCP client:
import { experimental_createMCPClient as createMCPClient } from 'ai';

onMessage: async (message, ctx) => {
  const mcp = await createMCPClient({
    transport: {
      type: 'http',
      url: 'https://mcp.example.com/mcp',
      headers: { Authorization: `Bearer ${yourAccessToken}` },
    },
  });

  return generateText({
    model: openai('gpt-4o'),
    messages: toModelMessages(ctx.history),
    tools: { ...(await mcp.tools()), ...localTools },
  });
},
Novu does not inject MCP credentials into ctx. Resolve tokens in your handler — for example, look up a per-subscriber token you stored after your own OAuth flow using ctx.subscriber?.subscriberId.

Typing indicator

Show a Thinking… or custom status while your handler runs.

Reply

Markdown, attachments, and interactive cards.

Tool approval

The shared Approve / Deny primitive.

LangChain

Return LangChain agent configs with automatic tool approval resume.

Other frameworks

Wire handlers without a Novu runtime adapter.