AI

Usage with the Vercel AI SDK

The @thirdweb-dev/ai-sdk-provider is a lightweight provider that lets you use thirdweb AI with the Vercel AI SDK.

It standardizes message parts, exposes wallet-aware tools (sign_transaction, sign_swap), and ships types/utilities so you can build chat UIs that execute on-chain actions from AI responses.

If you're using the Vercel AI SDK (ai / @ai-sdk/react) and want your agent to request blockchain actions safely, this provider gives you:

  • A server wrapper that streams AI output and tools.
  • A message schema (ThirdwebAiMessage) compatible with useChat for typed tool results.
  • Ready-made thirdweb tools wired for transactions, swaps and monitoring.

Installation

pnpm add @thirdweb-dev/ai-sdk-provider ai @ai-sdk/react

Usage

Server side (Next.js App Router, /app/api/chat/route.ts)

Create a thirdweb ai provider instance and compatible with the Vercel AI SDK by calling createThirdwebAI with your project secret key.

Then pass a thirdwebAI.chat() instance as the model for the streamText function and configure the model context with the context option.

For continuous conversations, you can extract the session id from the response and send it back to the client by overriding the messageMetadata function as shown below.

// src/app/api/chat/route.ts
import { convertToModelMessages, streamText } from "ai";
import { createThirdwebAI } from "@thirdweb-dev/ai-sdk-provider";
// Allow streaming responses up to 5 minutes
export const maxDuration = 300;
const thirdwebAI = createThirdwebAI({
secretKey: process.env.THIRDWEB_SECRET_KEY,
});
export async function POST(req: Request) {
const { messages, sessionId } = await req.json();
const result = streamText({
model: thirdwebAI.chat({
context: {
session_id: sessionId, // session id for continuity
chain_ids: [8453], // optional chain ids
from: "0x...", // optional from address
auto_execute_transactions: true, // optional, defaults to false
},
}),
messages: convertToModelMessages(messages),
tools: thirdwebAI.tools(), // optional, to handle transactions and swaps
});
return result.toUIMessageStreamResponse({
sendReasoning: true, // optional, to send reasoning steps to the client
messageMetadata({ part }) {
// record session id for continuity
if (part.type === "finish-step") {
return {
session_id: part.response.id,
};
}
},
});
}

Client side (React, using useChat)

Use useChat<ThirdwebAiMessage>() to get typed objects from useChat(). This will give you strongly typed tool results in your UI like tool-sign_transaction, tool-sign_swap or tool-monitor_transaction.

"use client";
import { useState } from "react";
import { useChat, DefaultChatTransport } from "@ai-sdk/react";
import type { ThirdwebAiMessage } from "@thirdweb-dev/ai-sdk-provider";
export function Chat() {
const [sessionId, setSessionId] = useState("");
const { messages, sendMessage, addToolResult, status } =
useChat<ThirdwebAiMessage>({
transport: new DefaultChatTransport({ api: "/api/chat" }),
onFinish: ({ message }) => {
// save session id for continuity
setSessionId(message.metadata?.session_id ?? "");
},
});
const send = (message: string) => {
sendMessage(
{ text: message },
{
body: {
// send session id for continuity
sessionId,
},
},
);
};
// You can render messages and reasoning steps as you normally would
// Use your own UI or the vercel ai sdk UI
// When a tool part arrives (e.g., message.parts[0].type === "tool-sign_transaction"),
// you can render a thirdweb `TransactionButton` wired to the provided input to execute the transaction.
return return (
<>
{messages.map((message) => (
<RenderMessage message={message} />
))}
<ChatInputBox send={send} />
</>
);;
}

Rendering messages and tool results

You can render messages and reasoning steps as you normally would using your own UI or the Vercel AI Elements components.

When a transaction or swap is requested, the model will return a tool-sign_transaction or tool-sign_swap part. You can then render those tool requests in your UI, for example:

  • tool-sign_transaction -> render a button to execute the transaction (TransactionButton works well for this)
  • tool-sign_swap -> render a button to execute the swap
  • tool-monitor_transaction -> render a loading indicator with the transaction status (when auto_execute_transactions is true)

Then on result, you can call the addToolResult function to add the tool result to the messages array and send it back to the model to continue the conversation.

export function RenderMessage(props: {
message: ThirdwebAiMessage;
sessionId: string;
addToolResult: (toolResult: ThirdwebAiMessage) => void;
sendMessage: (message: string) => void;
}) {
const { message, sessionId, addToolResult, sendMessage } = props;
return (
<>
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return <Text>{part.text}</Text>;
case "reasoning":
return <Reasoning>{part.text}</Reasoning>;
case "tool-sign_transaction":
// handle transaction confirmation UI here
// example:
const transactionData = part.input;
return (
<TransactionButton
transaction={() =>
prepareTransaction({
client: THIRDWEB_CLIENT,
chain: defineChain(transactionData.chain_id),
to: transactionData.to,
data: transactionData.data,
value: transactionData.value,
})
}
onTransactionSent={(transaction) => {
// add the tool result to the messages array
addToolResult({
tool: "sign_transaction",
toolCallId,
output: {
transaction_hash: transaction.transactionHash,
chain_id: transaction.chain.id,
},
});
// send the message to the model to continue the conversation
sendMessage(undefined, {
body: {
// send session id for continuity
sessionId,
},
});
}}
onError={(error) => {
// in case of error, send the error message to the model
sendMessage(
{ text: `Transaction failed: ${error.message}` },
{
body: {
sessionId,
},
},
);
}}
>
Sign Transaction
</TransactionButton>
);
case "tool-sign_swap":
// handle swap UI here
return <SwapButton input={part.input} />;
case "tool-monitor_transaction":
// for auto executing transactions, handle transaction monitoring UI here
return <TransactionMonitor input={part.input} />;
default:
return null;
}
})}
</>
);
}