This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/document-authoring/ai/integrations/vercel-ai-sdk.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Use Document Authoring AI with Vercel AI SDK

This guide covers the Vercel adapter that connects Document Authoring AI to Vercel AI SDK(opens in a new tab).

For the conceptual model, refer to the agentic tools guide, and for the Edit, Review, and View policy, refer to the review and approval guide.

Before you start

Before you use this guide, confirm that your app meets these requirements.

  • A Document Authoring editor runs in the browser.
  • Your app uses Vercel AI SDK for chat transport and the model loop.
  • Your server can reach your model provider.
  • The browser can access the live DocAuthEditor instance when tool calls arrive.

The examples use OpenAI through Vercel AI SDK, but any provider it supports works with the same pattern.

The toolkit doesn’t depend on the selected model provider.

Server route for agentic tools

The toolkit includes framework-neutral helpers in @nutrient-sdk/document-authoring-ai.

The Vercel adapter in @nutrient-sdk/document-authoring-ai/vercel converts those definitions into an AI SDK tool set that you pass to streamText(opens in a new tab).

import { openai } from "@ai-sdk/openai";
import {
convertToModelMessages,
stepCountIs,
streamText,
} from "ai";
import {
getAiPromptGuide,
getAiToolDefinitions,
} from "@nutrient-sdk/document-authoring-ai";
import { toVercelAiTools } from "@nutrient-sdk/document-authoring-ai/vercel";
const tools = toVercelAiTools(
getAiToolDefinitions({
reviewComments: "create",
}),
);
const system = `${getAiPromptGuide()}
Keep reviewComment values concise and reviewer-facing.`;
export async function POST(req: Request) {
const body = await req.json();
const result = streamText({
model: openai("gpt-5.4-mini"),
system,
messages: await convertToModelMessages(body.messages),
stopWhen: stepCountIs(20),
tools,
});
return result.toUIMessageStreamResponse();
}

The adapter returns tools without execute handlers, so the route exposes them to the model but doesn’t run them.

The browser runs the tools.

Use getAiPromptGuide as the base system prompt for these tools; it tells the model how the toolkit handles element IDs, read-before-write ordering, and other rules.

The prompt guide also explains when to include reviewComment.

The reviewComments: "create" option makes reviewComment required on write tool calls, so use the same option in the browser execution policy.

Browser chat loop

The useChat(opens in a new tab) hook receives the model’s tool calls in the browser.

The toolkit’s work inside onToolCall uses the same execution loop described in the agentic tools guide. The Vercel-specific step sends the result back through addToolOutput(opens in a new tab).

import { useChat } from "@ai-sdk/react";
import {
DefaultChatTransport,
lastAssistantMessageIsCompleteWithToolCalls,
} from "ai";
import {
isAiWriteToolName,
} from "@nutrient-sdk/document-authoring-ai";
import { getAiToolkit } from "@nutrient-sdk/document-authoring-ai/editor";
const toolkit = getAiToolkit(editor);
const { addToolOutput } = useChat({
transport: new DefaultChatTransport({ api: "/api/chat" }),
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
async onToolCall({ toolCall }) {
// Skip Vercel dynamic tools; they aren't part of this route's tool set.
if ("dynamic" in toolCall && toolCall.dynamic) {
return;
}
const editorMode = editor.getEditorMode();
const isWriteTool = isAiWriteToolName(toolCall.toolName);
if (isWriteTool && editorMode === "view") {
addToolOutput({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
state: "output-error",
errorText:
"The document is in View mode. Switch to Edit or Review mode before editing.",
});
return;
}
const executed = await toolkit.executeTool(
{
id: toolCall.toolCallId,
name: toolCall.toolName,
args: toolCall.input,
},
{
writeMode: editorMode === "review"
? "track_changes"
: "apply",
reviewComments: isWriteTool ? "create" : "disabled",
},
);
addToolOutput({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output: executed,
});
},
});

lastAssistantMessageIsCompleteWithToolCalls(opens in a new tab) resumes the Vercel chat loop after you submit every tool output.

The reviewComments option is a host policy, not a model decision. The model must supply reviewComment on write tool calls because the server-side tool schema requires it.

The browser still decides whether to create comment threads when it executes the call.

Server route for workflows

Workflows use Vercel’s structured-output API instead of tool calls.

The route uses prepareWorkflowRun(...) to prepare the request. generateText(opens in a new tab) and Output.object(opens in a new tab) produce focused edits; run.apply(...) assembles the replacement for the browser.

This example requires Document Authoring AI 2.0 and Document Authoring SDK 1.21.0 or later.

import { openai } from "@ai-sdk/openai";
import { generateText, Output } from "ai";
import {
getBuiltInWorkflow,
prepareWorkflowRun,
} from "@nutrient-sdk/document-authoring-ai";
import { toVercelAiWorkflowEditsSchema } from "@nutrient-sdk/document-authoring-ai/vercel";
export async function POST(req: Request) {
try {
const body: unknown = await req.json();
if (typeof body !== "object" || body === null || !("workflowInput" in body)) {
return Response.json({ error: "workflowInput is required." }, { status: 400 });
}
const workflow = getBuiltInWorkflow("proofreading");
const run = prepareWorkflowRun({ workflow, input: body.workflowInput });
const result = await generateText({
model: openai("gpt-5.4-mini"),
system: run.systemPrompt,
prompt: run.createPrompt(),
output: Output.object(toVercelAiWorkflowEditsSchema()),
});
return Response.json({ output: run.apply(result.output) });
} catch (error) {
console.error("Proofreading workflow failed", error);
return Response.json({ error: "The workflow could not be completed." }, { status: 500 });
}
}

On the browser side, use this workflow sequence.

  1. Call toolkit.readWorkflowInput to build the request body.
  2. Send the request body to this route.
  3. Apply the result with toolkit.applyWorkflowOutput.

For the full browser flow, refer to the workflows guide.