@agno-hq/chat-react
v0.1.0
Published
Headless React hook (useAgnoChat) + UI components for streaming Agno agents, teams, and workflows from an AgentOS backend.
Downloads
96
Readme
@agno-hq/chat-react
Headless React hook (useAgnoChat) plus drop-in UI components for streaming any
Agno agent, team, or workflow from an AgentOS backend.
It speaks the AgentOS HTTP run protocol directly: it POSTs to the run endpoint, parses the streamed events, and accumulates them into render-ready messages — content, tool calls, reasoning, citations, media — while exposing the raw event feed, live status, and human-in-the-loop pauses.
import { AgnoChat } from '@agno-hq/chat-react'
import '@agno-hq/chat-react/styles.css'
export default function Page() {
return <AgnoChat baseUrl="http://localhost:7777" showEventLog allowFiles />
}That's the zero-wiring widget: it discovers the agents/teams/workflows on your AgentOS, shows a selector, and runs a full chat. For full control, use the hook.
Install
npm install @agno-hq/chat-reactreact and react-dom (>=18) are peer dependencies. The library ships
compiled ESM + CommonJS bundles with bundled type declarations, so it works out
of the box in any modern bundler (Vite, Next.js, Webpack, etc.) with no extra
config. Import the stylesheet once (see below).
The hook: useAgnoChat
import { useAgnoChat, ChatWindow } from '@agno-hq/chat-react'
import '@agno-hq/chat-react/styles.css'
function Chat() {
const chat = useAgnoChat({
baseUrl: 'http://localhost:7777',
entity: { type: 'agent', id: 'agno_assist', name: 'Agno Assist' },
userId: 'user-123',
})
return <ChatWindow chat={chat} />
}entity.type is 'agent' | 'team' | 'workflow' and entity.id is the
agent/team/workflow id — the same hook drives all three.
What the hook returns
| Value | Type | Description |
|---|---|---|
| messages | ChatMessage[] | Full transcript, oldest first. |
| streamingMessage | ChatMessage \| null | The agent message currently streaming. |
| events | RunEventData[] | Every raw event from the latest run, in order. |
| currentEvent | RunEventData \| null | The most recent event. |
| status | 'idle' \| 'streaming' \| 'paused' \| 'completed' \| 'error' \| 'cancelled' | Run lifecycle. |
| activity | string \| null | Live label, e.g. "Calling get_weather", "Reasoning". |
| isStreaming / isPaused | boolean | Convenience flags. |
| error | string \| null | Last error message. |
| sessionId | string \| undefined | Auto-captured from the first run. |
| tools | ToolExecution[] | Tool calls of the active message. |
| reasoning | ReasoningStep[] | Reasoning steps of the active message. |
| pendingRequirements | RunRequirement[] | Outstanding human-in-the-loop asks. |
Actions
| Action | Description |
|---|---|
| sendMessage(text, { files? }) | Send a message and stream the response. |
| cancel() | Abort the active run (also calls the cancel endpoint). |
| continueRun({ tools?, stepRequirements? }) | Resume a paused run with resolved requirements. |
| respondToConfirmation(approve) | Approve/reject pending tool confirmations, then continue. |
| submitUserInput(values) | Provide values for pending input fields, then continue. |
| reset() | Clear the transcript and start a new session. |
| setMessages(...) | Replace the transcript (e.g. after restoring a session). |
| client | The underlying AgnoClient for discovery/session calls. |
Components
All components are styled by @agno-hq/chat-react/styles.css (dark by default; add
the agno-light class on a wrapper for light mode). Every piece is exported so
you can compose your own layout.
| Component | Purpose |
|---|---|
| <AgnoChat> | All-in-one widget: discovery, selector, chat, optional event log. |
| <ChatWindow chat={chat}> | Full chat surface built from a useAgnoChat result. |
| <MessageList> | Auto-scrolling transcript with live status + footer slot. |
| <Message> | A single message: content, tools, reasoning, media, citations. |
| <ChatInput> | Multiline input with file attach, send, and stop. |
| <ToolCalls> | Collapsible tool-call cards (name, args, result, status). |
| <Reasoning> | Collapsible reasoning-steps panel. |
| <Citations> | References / source URLs. |
| <Multimedia> | Images, video, and audio attachments. |
| <StatusIndicator> | Animated "what is it doing now" line. |
| <EventLog> | Developer feed of every raw run event. |
| <HumanInput> | Human-in-the-loop panel (confirm / reject / input). |
| <EntitySelector> | Dropdown of agents, teams, and workflows. |
| <SessionList> | Sidebar of past sessions — click to load, trash to delete. |
| <Markdown> | The built-in lightweight Markdown renderer. |
Bring your own Markdown
The built-in renderer covers code blocks, inline code, bold/italic, links,
headings and lists. For full GFM (tables, etc.), pass renderMarkdown:
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
<ChatWindow
chat={chat}
renderMarkdown={(c) => <ReactMarkdown remarkPlugins={[remarkGfm]}>{c}</ReactMarkdown>}
/>Human-in-the-loop
When a run pauses for a tool confirmation or for user input, status becomes
'paused' and pendingRequirements / the paused message's tool_calls
describe what's needed. <ChatWindow> renders <HumanInput> automatically; to
build your own UI, call:
await chat.respondToConfirmation(true) // approve pending tool calls
await chat.respondToConfirmation(false) // reject
await chat.submitUserInput({ city: 'Lisbon' }) // fill input fields, then continueAgents and teams resume via the /continue endpoint with resolved tools;
workflows resume with step_requirements. The hook picks the right one based on
the selected entity type.
Session history
The hook tracks past sessions for the selected entity:
const chat = useAgnoChat({ baseUrl, entity })
chat.sessions // SessionEntry[]
chat.sessionsLoading // boolean
await chat.refreshSessions() // fetch the list
await chat.loadSession(sessionId) // load a transcript into the chat
await chat.deleteSession(sessionId)Render them with <SessionList>:
<SessionList
sessions={chat.sessions}
activeSessionId={chat.sessionId}
loading={chat.sessionsLoading}
onSelect={chat.loadSession}
onDelete={chat.deleteSession}
onNew={chat.reset}
/>The all-in-one widget shows this sidebar with showSessions:
<AgnoChat baseUrl="http://localhost:7777" showSessions showEventLog />Lower-level API
import { AgnoClient, streamRun } from '@agno-hq/chat-react'
const client = new AgnoClient({ baseUrl: 'http://localhost:7777', headers: { Authorization: 'Bearer …' } })
await client.getEntities() // agents + teams + workflows
await client.getSessions('agent', 'agno_assist')
await client.getSessionRuns('agent', sessionId) // rehydrate history
await client.cancelRun('agent', 'agno_assist', runId)streamRun is the raw streaming primitive (parses the wire format and emits
normalised events) if you want to bypass the hook entirely.
Running the example
The example/ folder is a Vite app demonstrating both the widget and the hook
(with a live status panel + event log). It imports the library from source.
cd example
npm install
npm run devThen open the app, set your AgentOS URL (default http://localhost:7777), and
pick an agent, team, or workflow.
Start an AgentOS first — see the Agno cookbooks under
cookbook/05_agent_os/.
CORS
The chat runs in the browser, so your AgentOS must allow the page's origin.
The example dev server is pinned to port 5173 (strictPort), so add that
origin when constructing AgentOS:
AgentOS(..., cors_allowed_origins=["http://localhost:5173"])A symptom of a CORS mismatch is an empty entity dropdown and a
Disallowed CORS origin response to the preflight request.
Notes
- Auth / headers — pass
headers(or a pre-builtclient) to send anAuthorizationheader on every request. - Sessions —
sessionIdis captured automatically on the first run. To restore history, callclient.getSessionRuns(...)and map runs intoChatMessage[], thenchat.setMessages(...). - Wire format — handles both AgentOS streaming shapes (the legacy flat event
objects and the
{ event, data }SSE envelope). - Dependencies — the library itself depends only on React (peer). The example additionally uses Vite.
Local development
git clone https://github.com/agno-agi/agno-chat-react.git
cd agno-chat-react
npm install
npm run typecheck # tsc --noEmit
npm run build # tsup -> dist/ (ESM + CJS + .d.ts)The published package is built with tsup into
dist/ (ESM index.js, CommonJS index.cjs, type declarations, and
styles.css). The example/ app, however, resolves the library straight from
src/ via a Vite alias, so you can develop against live changes without
rebuilding — see Running the example. prepublishOnly
runs the typecheck and build automatically, so npm publish always ships a
fresh dist/.
License
MIT © Agno
