@antipopp/agno-react
React hooks for Agno client with full TypeScript support.
Installation
npm install @antipopp/agno-react
This package includes @antipopp/agno-client and @antipopp/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
- ✅ 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 '@antipopp/agno-react';
function App() {
return (
<AgnoProvider
config={{
endpoint: 'http://localhost:7777',
mode: 'agent',
agentId: 'your-agent-id',
userId: 'user-123', // Optional: Link sessions to a user
headers: { // Optional: Global headers for all requests
'X-API-Version': 'v2'
},
params: { // Optional: Global query params for all requests
locale: 'en-US'
},
dependencies: { // Optional: Global run dependencies for sendMessage
tenantId: 'tenant-1',
locale: 'en-US'
}
}}
>
<YourComponents />
</AgnoProvider>
);
}
2. Use Hooks in Your Components
import { useAgnoChat, useAgnoActions } from '@antipopp/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>
);
}
API Reference
AgnoProvider
Provider component that creates and manages an AgnoClient instance.
<AgnoProvider config={config}>
{children}
</AgnoProvider>
Props:
config (AgnoClientConfig) - Client configuration
children (ReactNode) - Child components
useAgnoClient()
Access the underlying AgnoClient instance.
const client = useAgnoClient();
await client.sendMessage('Hello!');
useAgnoChat()
Main hook for chat interactions.
const {
messages,
sendMessage,
clearMessages,
isStreaming,
error,
state,
} = useAgnoChat();
Methods:
sendMessage(message, options?)
await sendMessage('Hello!');
const formData = new FormData();
formData.append('message', 'Hello!');
formData.append('files', file);
await sendMessage(formData);
await sendMessage('Hello!', {
files: [file]
});
await sendMessage('Hello!', {
headers: { 'X-Custom': 'value' }
});
await sendMessage('Hello!', {
params: { temperature: '0.7', max_tokens: '500' }
});
await sendMessage('Hello!', {
headers: { 'X-Request-ID': '12345' },
params: { debug: 'true' }
});
await sendMessage('Hello!', {
dependencies: {
locale: 'fr-FR',
feature: 'rag'
}
});
clearMessages()
clearMessages();
useAgnoSession()
Hook for session management.
const {
sessions,
currentSessionId,
loadSession,
fetchSessions,
isLoading,
error,
} = useAgnoSession();
Example:
function SessionList() {
const { sessions, loadSession, fetchSessions } = useAgnoSession();
useEffect(() => {
fetchSessions({ params: { limit: '50', status: 'active' } }).then(
({ meta }) => {
console.log(`Fetched page ${meta.page} of ${meta.total_pages}`);
}
);
}, [fetchSessions]);
const handleLoadSession = (sessionId: string) => {
loadSession(sessionId, { params: { include_metadata: 'true' } });
};
return (
<ul>
{sessions.map((session) => (
<li key={session.session_id}>
<button onClick={() => handleLoadSession(session.session_id)}>
{session.session_name}
</button>
</li>
))}
</ul>
);
}
useAgnoActions()
Hook for common actions and initialization.
const {
initialize,
checkStatus,
fetchAgents,
fetchTeams,
updateConfig,
isInitializing,
error,
} = useAgnoActions();
Example:
function InitComponent() {
const { initialize, fetchAgents, updateConfig, isInitializing } = useAgnoActions();
const { state } = useAgnoChat();
useEffect(() => {
initialize({ params: { filter: 'active' } });
}, [initialize]);
const loadMoreAgents = () => {
fetchAgents({ params: { page: '2', limit: '20' } });
};
const switchAgent = (agentId: string) => {
updateConfig({ agentId, mode: 'agent' });
};
if (isInitializing) return <div>Loading...</div>;
return (
<div>
<h3>Agents</h3>
{state.agents.map((agent) => (
<button key={agent.id} onClick={() => switchAgent(agent.id)}>
{agent.name}
</button>
))}
<button onClick={loadMoreAgents}>Load More</button>
</div>
);
}
useAgnoToolExecution()
Hook for Human-in-the-Loop (HITL) frontend tool execution.
import {
createToolArgsValidatorFromSafeParse,
createValidatedToolHandler,
type ToolHandler,
useAgnoToolExecution,
} from '@antipopp/agno-react';
import { z } from 'zod';
const navigateSchema = z.object({
url: z.string().url(),
});
const navigateValidator = createToolArgsValidatorFromSafeParse((args) =>
navigateSchema.safeParse(args)
);
const toolHandlers: Record<string, ToolHandler> = {
navigate_to_page: createValidatedToolHandler(navigateValidator, (args) => {
window.location.href = args.url;
return { success: true, url: args.url };
}),
};
function ChatWithTools() {
const { isPaused, isExecuting, pendingTools, executionError } =
useAgnoToolExecution(toolHandlers, true);
return <div>{isExecuting ? `Executing ${pendingTools.length} tools...` : null}</div>;
}
Scope of this hook:
- Listens to paused run events and tracks execution state for your UI
- Executes local/global tool handlers and continues paused runs
- Supports auto execution or manual approval flows
- Hydrates tool-call UI components when sessions are loaded
Validation note:
- Tool arguments are runtime input. Prefer
createValidatedToolHandler() with schema validation to avoid repeating manual guards.
Complete Example
import { useState, useEffect } from 'react';
import {
AgnoProvider,
useAgnoChat,
useAgnoSession,
useAgnoActions,
} from '@antipopp/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 '@antipopp/agno-react';
License
MIT