Vercel AI SDK
Create custom UIs using the Vercel AI SDK Use the Vercel AI SDK's useChat hook to build React chat interfaces that stream responses from your agent.
Agents built with Inkeep have an /api/chat that is compatible with Vercel AI SDK's streaming protocol, so it works out of the box with Vercel's AI SDK ecosystem.
To build totally custom UIs, you have two options in the AI SDK ecosystem:
Use useChat hook to build custom UIs in React, Svelte, Vue.js, and Angular. (← this guide )
Use the library for a shadcn-like chat primitives.
Install the Vercel AI SDK in your React application:
npm install ai @ai-sdk/react
Here's a minimal example of using useChat with your agent:
"use client" ;
import { useChat } from "@ai-sdk/react" ;
import { DefaultChatTransport } from "ai" ;
import { useState } from "react" ;
export default function Page () {
const { messages , sendMessage } = useChat ({
transport : new DefaultChatTransport ({
api : "${process.env.NEXT_PUBLIC_INKEEP_AGENTS_RUN_API_URL}/api/chat" , //
headers : {
"x-inkeep-tenant-id" : "default" ,
"x-inkeep-project-id" : "activities-planner" ,
"x-inkeep-agent-id" : "activities-planner" ,
},
}),
});
const [ input , setInput ] = useState ( "" );
return (
< div className = "max-w-2xl mx-auto p-4" >
< div className = "space-y-4 mb-4" >
{ messages . map (( message ) => (
< div key = { message . id } className = "border rounded p-3" >
< div className = "font-semibold mb-2" >
{ message . role === "user" ? "👤 User" : "🤖 Assistant" }
</ div >
< div className = "space-y-2" >
{ message . parts . map (( part , partIndex ) => {
const partKey = ` ${ message . id } - ${ part . type } - ${ partIndex } ` ;
if ( part . type === "text" ) {
return (
< div key = { partKey } className = "whitespace-pre-wrap" >
{ part . text }
</ div >
);
}
return null ;
}) }
</ div >
</ div >
)) }
</ div >
< form
onSubmit = { ( e ) => {
e . preventDefault ();
if ( input . trim ()) {
sendMessage ({ text : input });
setInput ( "" );
}
} }
className = "flex gap-2"
>
< input
value = { input }
onChange = { ( e ) => setInput ( e . target . value ) }
placeholder = "Ask about weather, request confirmation, or ask for location..."
className = "flex-1 p-2 border rounded"
/>
< button
type = "submit"
className = "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Send
</ button >
</ form >
< div className = "mt-4 text-sm text-gray-600" >
< p >Try asking:</ p >
< ul className = "list-disc list-inside space-y-1" >
< li >"What's the weather in New York?"</ li >
< li >"Can you get my location?"</ li >
< li >"Ask me to confirm something"</ li >
</ ul >
</ div >
</ div >
);
}
The api parameter should point to your Run API's chat endpoint:
Local development : http://localhost:3003/api/chat
Production : https://your-run-api-domain.com/api/chat
Choose the authentication method:
See for more details.
const { messages , sendMessage , status } = useChat ({
api : 'http://localhost:3003/api/chat' ,
headers : { /* ... */ },
// Handle completion
onFinish : ( message ) => {
console . log ( 'Agent finished responding:' , message );
},
// Handle errors
onError : ( error ) => {
console . error ( 'Chat error:' , error );
},
});
To pass required headers (validated against your Context Config), include them as custom headers:
const { messages , sendMessage } = useChat ({
api : 'http://localhost:3003/api/chat' ,
headers : {
'Authorization' : `Bearer ${ apiKey } ` ,
// Custom context headers
'x-user-id' : 'user-123' ,
'x-organization-id' : 'org-456' ,
},
});
Here's a more complete example with conversation management and custom styling:
"use client" ;
import { useChat } from "@ai-sdk/react" ;
import { DefaultChatTransport } from "ai" ;
import { useState } from "react" ;
export default function Page () {
const { messages , sendMessage } = useChat ({
transport : new DefaultChatTransport ({
api : "{process.env.NEXT_PUBLIC_INKEEP_AGENTS_RUN_API_URL}/api/chat" ,
headers : {
"x-inkeep-tenant-id" : "default" ,
"x-inkeep-project-id" : "activities-planner" ,
"x-inkeep-agent-id" : "activities-planner" ,
tz : "US/Pacific" ,
},
}),
});
const [ input , setInput ] = useState ( "" );
return (
< div className = "max-w-2xl mx-auto p-4" >
< div className = "space-y-4 mb-4" >
{ messages . map (( message ) => (
< div key = { message . id } className = "border rounded p-3" >
< div className = "font-semibold mb-2" >
{ message . role === "user" ? "👤 User" : "🤖 Assistant" }
</ div >
< div className = "space-y-2" >
{ message . parts . map (( part , partIndex ) => {
const partKey = ` ${ message . id } - ${ part . type } - ${ partIndex } ` ;
if ( part . type === "text" ) {
return (
< div key = { partKey } className = "whitespace-pre-wrap" >
{ part . text }
</ div >
);
}
if ( part . type ?. startsWith ( "data-" )) {
return (
< div
key = { partKey }
className = "bg-blue-50 p-3 rounded border-l-4 border-blue-400"
>
< div className = "font-semibold text-blue-700" >
Data: { JSON . stringify ( part , null , 2 ) }
</ div >
</ div >
);
}
return null ;
}) }
</ div >
</ div >
)) }
</ div >
< form
onSubmit = { ( e ) => {
e . preventDefault ();
if ( input . trim ()) {
sendMessage ({ text : input });
setInput ( "" );
}
} }
className = "flex gap-2"
>
< input
value = { input }
onChange = { ( e ) => setInput ( e . target . value ) }
placeholder = "Ask about weather, request confirmation, or ask for location..."
className = "flex-1 p-2 border rounded"
/>
< button
type = "submit"
className = "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Send
</ button >
</ form >
< div className = "mt-4 text-sm text-gray-600" >
< p >Try asking:</ p >
< ul className = "list-disc list-inside space-y-1" >
< li >"What's the weather in New York?"</ li >
< li >"Can you get my location?"</ li >
< li >"Ask me to confirm something"</ li >
</ ul >
</ div >
</ div >
);
}
For production deployments, store configuration in environment variables:
NEXT_PUBLIC_INKEEP_AGENTS_RUN_API_URL = your_domain_for_run_api
NEXT_PUBLIC_INKEEP_API_KEY = your-api-key
NEXT_PUBLIC_TENANT_ID = your-tenant-id
NEXT_PUBLIC_PROJECT_ID = your-project-id
NEXT_PUBLIC_AGENT_ID = your-agent-id
In local development, the Run API runs at http://localhost:3003. For production, update this to your deployed Run API URL.