@rodrigocoliveira/agno-react
v2.3.0
Published
React hooks and pre-built UI components for Agno client
Maintainers
Readme
@rodrigocoliveira/agno-react
React hooks and pre-built UI components for Agno client with full TypeScript support.
Installation
npm install @rodrigocoliveira/agno-reactThis package includes @rodrigocoliveira/agno-client and @rodrigocoliveira/agno-types as dependencies.
Features
- Easy Integration — Drop-in React hooks for Agno agents
- Context Provider — Manages client lifecycle automatically
- Real-time Updates — React state synced with streaming updates
- Pre-built UI Components — Compound components and primitives via
/uisub-path - Audio Recording & Transcription — Record audio to send or transcribe to text
- Frontend Tool Execution (HITL) — Execute agent tools in the browser
- Type-Safe — Full TypeScript support
- Familiar API — Matches the original Agno React hooks design
Quick Start
1. Wrap Your App with AgnoProvider
import { AgnoProvider } from '@rodrigocoliveira/agno-react';
function App() {
return (
<AgnoProvider
config={{
endpoint: 'http://localhost:7777',
mode: 'agent',
agentId: 'your-agent-id',
userId: 'user-123',
headers: { 'X-API-Version': 'v2' },
params: { locale: 'en-US' }
}}
>
<YourComponents />
</AgnoProvider>
);
}2. Use Hooks in Your Components
import { useAgnoChat, useAgnoActions } from '@rodrigocoliveira/agno-react';
function ChatComponent() {
const { messages, sendMessage, isStreaming, error } = useAgnoChat();
const { initialize } = useAgnoActions();
useEffect(() => {
initialize();
}, [initialize]);
const handleSend = async () => {
await sendMessage('Hello, agent!');
};
return (
<div>
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{error && <div>Error: {error}</div>}
<button onClick={handleSend} disabled={isStreaming}>
{isStreaming ? 'Sending...' : 'Send'}
</button>
</div>
);
}3. Or Use Pre-built UI Components
For a full-featured chat interface with minimal code, use the compound components from the /ui sub-path:
import { AgnoChat } from '@rodrigocoliveira/agno-react/ui';
function ChatPage() {
return (
<AgnoChat>
<AgnoChat.Messages>
<AgnoChat.EmptyState>
<h3>Welcome!</h3>
<p>Start a conversation with the agent.</p>
<AgnoChat.SuggestedPrompts
prompts={[
{ text: 'What can you help me with?' },
{ text: 'Show me a code example' },
]}
/>
</AgnoChat.EmptyState>
</AgnoChat.Messages>
<AgnoChat.ErrorBar />
<AgnoChat.Input placeholder="Ask me anything..." />
</AgnoChat>
);
}API Reference
AgnoProvider
Provider component that creates and manages an AgnoClient instance.
<AgnoProvider config={config}>
{children}
</AgnoProvider>Props:
config(AgnoClientConfig) — Client configurationchildren(ReactNode) — Child components
useAgnoClient()
Access the underlying AgnoClient instance.
const client = useAgnoClient();
// Use client methods directly
await client.sendMessage('Hello!');useAgnoChat()
Main hook for chat interactions.
const {
messages, // ChatMessage[] - Current messages
sendMessage, // (message, options?) => Promise<void>
clearMessages, // () => void
isStreaming, // boolean - Is currently streaming
error, // string | undefined - Current error
state, // ClientState - Full client state
} = useAgnoChat();Methods:
sendMessage(message, options?)
// Send a text message
await sendMessage('Hello!');
// Send with FormData (for file uploads)
const formData = new FormData();
formData.append('message', 'Hello!');
formData.append('file', file);
await sendMessage(formData);
// Send with custom headers
await sendMessage('Hello!', {
headers: { 'X-Custom': 'value' }
});
// Send with query parameters
await sendMessage('Hello!', {
params: { temperature: '0.7', max_tokens: '500' }
});clearMessages()
clearMessages(); // Clears all messages and resets sessionuseAgnoSession()
Hook for session management.
const {
sessions, // SessionEntry[] - Available sessions
currentSessionId, // string | undefined - Current session ID
loadSession, // (sessionId, options?) => Promise<ChatMessage[]>
fetchSessions, // (options?) => Promise<SessionEntry[]>
isLoading, // boolean - Is loading session
error, // string | undefined - Current error
} = useAgnoSession();useAgnoActions()
Hook for common actions and initialization.
const {
initialize, // (options?) => Promise<{ agents, teams }>
checkStatus, // (options?) => Promise<boolean>
fetchAgents, // (options?) => Promise<AgentDetails[]>
fetchTeams, // (options?) => Promise<TeamDetails[]>
updateConfig, // (updates) => void
isInitializing, // boolean
error, // string | undefined
} = useAgnoActions();useAgnoToolExecution()
Hook for frontend tool execution (Human-in-the-Loop).
const toolHandlers = {
show_alert: async (args) => {
alert(args.content);
return { success: true };
},
};
// Auto-execute tools immediately
useAgnoToolExecution(toolHandlers);
// Or require manual confirmation
useAgnoToolExecution(toolHandlers, false);useAgnoMemory()
Hook for memory management.
const {
memories,
topics,
isLoading,
fetchMemories,
createMemory,
updateMemory,
deleteMemory,
} = useAgnoMemory();useAgnoCustomEvents()
Hook for listening to custom events yielded by the backend.
useAgnoCustomEvents((event) => {
console.log('Custom event:', event);
});Pre-built UI Components (/ui sub-path)
The library ships with a complete set of pre-built UI components accessible via @rodrigocoliveira/agno-react/ui. These components provide a production-ready chat interface with full customization support.
Peer Dependencies
The library ships UI as peer dependencies so your app controls the versions and avoids duplicate React instances. Required vs optional depends on which import path you use.
If you only import from @rodrigocoliveira/agno-react (hooks-only, no UI): zero peer deps beyond React itself.
If you import anything from @rodrigocoliveira/agno-react/ui: the following are required.
# Required by /ui (used by <AgnoChat> / <Conversation> / <Response>)
npm install use-stick-to-bottom streamdown
# Core styling / variant utilities — required by every component in /ui
npm install class-variance-authority clsx tailwind-merge lucide-reactPer-feature optional peer deps — install only the ones whose components you actually import:
| If you use… | Install |
| --- | --- |
| <AgnoChat.Input> with attachments / model select / command palette | @radix-ui/react-slot @radix-ui/react-dropdown-menu cmdk |
| <Tooltip> | @radix-ui/react-tooltip |
| <Avatar> | @radix-ui/react-avatar |
| <Accordion> | @radix-ui/react-accordion |
| <Collapsible> (used by <Tool> and tool debug card) | @radix-ui/react-collapsible |
| <HoverCard> | @radix-ui/react-hover-card |
| <Select> | @radix-ui/react-select |
| <Dialog> / <AskUserQuestionModal> patterns | @radix-ui/react-dialog |
| Code-block syntax highlighting in <Response> | shiki (auto-loaded by streamdown if installed) |
| <BarChart> / <LineChart> / <AreaChart> / <PieChart> | recharts |
Quick install for "I want everything that ships in /ui":
npm install use-stick-to-bottom streamdown shiki recharts \
class-variance-authority clsx tailwind-merge lucide-react cmdk \
@radix-ui/react-accordion @radix-ui/react-avatar @radix-ui/react-collapsible \
@radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-hover-card \
@radix-ui/react-select @radix-ui/react-slot @radix-ui/react-tooltipNote:
use-stick-to-bottomandstreamdownwere markedoptionalin 2.1.0 and earlier, which preventednpm installfrom warning when they were missing — consumers using/uiwould then hit a runtime "Could not resolve …" error. As of 2.1.1 they are required peer deps andnpm installreports them up front.
Import Path
All UI components are imported from the /ui sub-path:
import { AgnoChat, AgnoChatInput, AgnoMessage, byToolName, Button, Response } from '@rodrigocoliveira/agno-react/ui';AgnoChat (Compound Component)
The primary way to build a full-featured chat interface. Uses a compound component pattern — compose only the pieces you need.
import { AgnoChat, byToolName } from '@rodrigocoliveira/agno-react/ui';
import type { ToolHandler, RenderTool } from '@rodrigocoliveira/agno-react';
const toolHandlers: Record<string, ToolHandler> = {
show_alert: async (args) => {
alert(args.content);
return { success: true };
},
};
// Optional: customize per-tool rendering. `byToolName` is a helper that
// dispatches by `tool.tool_name`; unlisted tools fall through to the default.
const renderTool: RenderTool = byToolName({
internal_log: false, // hide entirely
search_flights: (tool) => <FlightResults tool={tool} />, // custom widget
});
function ChatPage() {
return (
<AgnoChat
toolHandlers={toolHandlers}
autoExecuteTools={true}
renderTool={renderTool}
// debug auto-detects from NODE_ENV; set explicitly to force the debug
// tool card on/off (e.g. enable in prod to investigate a live bug).
debug={false}
>
<AgnoChat.Messages
avatars={{
user: <img src="/user.png" className="h-8 w-8 rounded-full" />,
assistant: <img src="/bot.png" className="h-8 w-8 rounded-full" />,
}}
actions={{
visibility: 'hover-last-visible',
assistant: (message) => (
<button onClick={() => navigator.clipboard.writeText(message.content || '')}>
Copy
</button>
),
}}
showReasoning={false}
>
<AgnoChat.EmptyState>
<h3>Welcome!</h3>
<p>How can I help you today?</p>
<AgnoChat.SuggestedPrompts
prompts={[
{ icon: <span>⚡</span>, text: 'What can you help me with?' },
{ icon: <span>💡</span>, text: 'Show me a code example' },
]}
/>
</AgnoChat.EmptyState>
</AgnoChat.Messages>
<AgnoChat.ErrorBar className="bg-red-500/5" />
<AgnoChat.Input
placeholder="Ask me anything..."
showAudioRecorder={true}
audioMode="transcribe"
transcriptionEndpoint="http://localhost:8000/transcribe"
/>
</AgnoChat>
);
}For custom message layouts (reordering slots, replacing sections), use the
<AgnoMessage> compound components via renderMessage. See
docs/tool-rendering.md for the full reference.
Sub-components:
| Component | Description |
|-----------|-------------|
| AgnoChat | Root wrapper. Accepts toolHandlers, autoExecuteTools, renderTool, debug, skipToolsOnSessionLoad. |
| AgnoChat.Messages | Message list with auto-scroll. Accepts avatars, actions, showReasoning, showReferences, showTimestamp, renderMessage, renderTool, and more. |
| AgnoChat.EmptyState | Shown when there are no messages. Place inside Messages. |
| AgnoChat.SuggestedPrompts | Clickable prompt suggestions. Place inside EmptyState. |
| AgnoChat.ErrorBar | Error display bar. |
| AgnoChat.Input | Chat input with file uploads and optional audio recorder. |
AgnoChatInput
Standalone chat input component with file uploads, audio recording, and transcription support.
import { AgnoChatInput } from '@rodrigocoliveira/agno-react/ui';
<AgnoChatInput
onSend={(message) => { /* handle message */ }}
placeholder="Type a message..."
showAudioRecorder={true}
showAttachments={true}
audioMode="transcribe"
transcriptionEndpoint="http://localhost:8000/transcribe"
parseTranscriptionResponse={(data) => data.text}
onRequestPermission={async () => {
// WebView: request mic permission from native bridge
return await NativeBridge.requestMicPermission();
}}
fileUpload={{
accept: 'image/*,.pdf',
multiple: true,
maxFiles: 5,
maxFileSize: 10 * 1024 * 1024,
}}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onSend | (message: string \| FormData) => void | required | Called when the user sends a message |
| disabled | boolean | false | Disable the input |
| placeholder | string | — | Input placeholder text |
| showAudioRecorder | boolean | false | Show the audio recorder button |
| showAttachments | boolean | true | Show the file attachment button |
| audioMode | 'send' \| 'transcribe' | 'send' | Audio recording behavior |
| transcriptionEndpoint | string | — | URL to POST audio for transcription (required when audioMode='transcribe') |
| transcriptionHeaders | Record<string, string> | — | Extra headers for transcription requests |
| parseTranscriptionResponse | (data: unknown) => string | — | Custom parser for transcription API response |
| onRequestPermission | () => Promise<boolean> | — | WebView mic permission bridge callback |
| fileUpload | FileUploadConfig | — | File upload configuration |
| status | ChatStatus | — | Input status ('idle', 'submitted', 'streaming', 'error') |
| extraTools | ReactNode | — | Additional toolbar buttons |
Audio Recorder & Transcription
The library includes an AudioRecorder component that supports two modes:
Send mode (default)
Records audio, encodes to WAV, and sends the blob directly as a file attachment:
<AgnoChatInput
onSend={handleSend}
showAudioRecorder={true}
audioMode="send"
/>The audio blob is wrapped in a FormData with message="Audio message" and the WAV file.
Transcribe mode
Records audio, sends it to a transcription endpoint, and inserts the resulting text into the input:
<AgnoChatInput
onSend={handleSend}
showAudioRecorder={true}
audioMode="transcribe"
transcriptionEndpoint="http://localhost:8000/transcribe"
parseTranscriptionResponse={(data) => data.text}
/>The component POSTs the WAV file to the endpoint and expects a JSON response. The default parser checks data.text, data.transcript, and data.transcription fields. Provide parseTranscriptionResponse to handle custom response shapes.
WebView Permission Bridging
For WebView environments where microphone access requires a native bridge:
<AgnoChatInput
onSend={handleSend}
showAudioRecorder={true}
onRequestPermission={async () => {
// Ask the native app for mic permission before getUserMedia
return await NativeBridge.requestMicPermission();
}}
/>The onRequestPermission callback is called before the browser's getUserMedia. Return true to proceed or false to cancel.
Primitive Components
Thin wrappers over Radix UI primitives with Tailwind styling via class-variance-authority:
| Component | Description |
|-----------|-------------|
| Button | Button with variants (default, outline, ghost, destructive, etc.) |
| Badge | Status badge with variants |
| Avatar, AvatarImage, AvatarFallback | User/assistant avatar |
| InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupTextarea | Composable input groups |
| Collapsible, CollapsibleTrigger, CollapsibleContent | Expandable sections |
| Tooltip, TooltipTrigger, TooltipContent, TooltipProvider | Hover tooltips |
| Accordion, AccordionItem, AccordionTrigger, AccordionContent | Collapsible accordion |
| DropdownMenu + sub-parts | Dropdown menu |
| HoverCard, HoverCardTrigger, HoverCardContent | Hover card |
| Select + sub-parts | Select dropdown |
| Command, CommandInput, CommandList, CommandItem, ... | Command palette (cmdk) |
Base Components
Higher-level components for building chat interfaces:
| Component | Description |
|-----------|-------------|
| Message, MessageContent, MessageAvatar | Low-level message layout shell |
| Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton | Scrollable conversation container with auto-scroll |
| Response | Markdown renderer with syntax highlighting (shiki + streamdown) |
| Tool, ToolHeader, ToolContent, ToolInput, ToolOutput | Collapsible tool call display |
| CodeBlock, CodeBlockCopyButton | Syntax-highlighted code block with copy button |
| Artifact, ArtifactHeader, ArtifactContent, ... | Artifact panel layout |
| StreamingIndicator | Animated typing/loading indicator |
| AudioRecorder | Audio recording with WAV encoding via AudioWorklet |
| PromptInput + sub-parts | Fully composable input system with attachments, speech, model select |
Complete Example
import { useState, useEffect } from 'react';
import {
AgnoProvider,
useAgnoChat,
useAgnoSession,
useAgnoActions,
} from '@rodrigocoliveira/agno-react';
function App() {
return (
<AgnoProvider
config={{
endpoint: 'http://localhost:7777',
mode: 'agent',
agentId: 'my-agent',
}}
>
<ChatApp />
</AgnoProvider>
);
}
function ChatApp() {
const [input, setInput] = useState('');
const { messages, sendMessage, isStreaming, error, clearMessages } = useAgnoChat();
const { sessions, loadSession, fetchSessions } = useAgnoSession();
const { initialize, state } = useAgnoActions();
useEffect(() => {
initialize().then(() => fetchSessions());
}, [initialize, fetchSessions]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isStreaming) return;
await sendMessage(input);
setInput('');
};
return (
<div>
<aside>
<h2>Sessions</h2>
<button onClick={() => clearMessages()}>New Chat</button>
<ul>
{sessions.map((session) => (
<li key={session.session_id}>
<button onClick={() => loadSession(session.session_id)}>
{session.session_name}
</button>
</li>
))}
</ul>
</aside>
<main>
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={`message ${msg.role}`}>
<strong>{msg.role}:</strong>
<p>{msg.content}</p>
{msg.tool_calls && (
<details>
<summary>Tool Calls</summary>
<pre>{JSON.stringify(msg.tool_calls, null, 2)}</pre>
</details>
)}
</div>
))}
{error && <div className="error">Error: {error}</div>}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
disabled={isStreaming}
/>
<button type="submit" disabled={isStreaming}>
{isStreaming ? 'Sending...' : 'Send'}
</button>
</form>
</main>
</div>
);
}
export default App;TypeScript
All hooks and components are fully typed. Import types as needed:
import type {
AgnoClientConfig,
ChatMessage,
SessionEntry,
AgentDetails,
TeamDetails,
} from '@rodrigocoliveira/agno-react';Publishing
To publish this package to npm:
# Login to npm (first time only)
npm login
# Build the package
bun run build
# Publish (use --access public for scoped packages)
npm publish --access publicPublish order: This package depends on both @rodrigocoliveira/agno-types and @rodrigocoliveira/agno-client, so publish them first:
@rodrigocoliveira/agno-types@rodrigocoliveira/agno-client@rodrigocoliveira/agno-react(this package)
License
MIT
