Skip to main content

Headless mode

You can use AgentProvider + useAgentChat() without any SDK UI components to build a fully custom chat interface:

import { AgentProvider, useAgentChat } from '@kapaai/agent-react';

const CustomChat = () => {
const {
messages,
isStreaming,
inputValue,
setInputValue,
sendMessage,
resetConversation,
stopGeneration,
approveToolCall,
rejectToolCall,
} = useAgentChat();

return (
<div>
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage(inputValue);
}
}}
/>
</div>
);
};

function App() {
return (
<AgentProvider
getSessionToken={async () => {
const res = await fetch('/api/session', { method: 'POST' });
return res.json();
}}
projectId="your-project-id"
integrationId="your-integration-id"
model="kapa-agent-1.0"
>
<CustomChat />
</AgentProvider>
);
}

In headless mode you handle all rendering, including message bubbles, tool call cards, approval buttons, and streaming indicators. The hook gives you the data and actions, you build the UI.

For the message data structure (ConversationMessage, ContentBlock, ToolCallDisplay, ToolCallStatus), see Message format in the core SDK docs. The types are the same. @kapaai/agent-react re-exports them from @kapaai/agent-core.

useAgentChat() return value

FieldTypeDescription
messagesConversationMessage[]All messages in the conversation.
isStreamingbooleanWhether the agent is currently streaming a response.
threadIdstring | nullCurrent conversation thread ID.
inputValuestringCurrent input field value.
setInputValue(value: string) => voidUpdate the input field value.
sendMessage(text: string) => Promise<void>Send a message and trigger the agent loop.
resetConversation() => voidClear messages and abort any in-progress request.
stopGeneration() => voidAbort the current streaming response.
approveToolCall(id: string) => voidApprove a tool waiting for confirmation.
rejectToolCall(id: string) => voidReject a tool waiting for confirmation.
getFaviconUrl(sourceUrl: string) => Promise<string>Fetch a favicon for a source URL. Returns a blob URL.
listThreads(options?) => Promise<ThreadListResult>List past conversations. Requires external_owner_id on the session. See Conversation history.
resumeThread(threadId: string) => Promise<void>Load a past conversation and replace the current chat state.
deleteThread(threadId: string) => Promise<void>Delete a thread. Resets the chat if the thread is currently active.
historyDisabledbooleanWhether conversation history is disabled (mirrors the enableHistory prop on AgentProvider).