npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rodrigocoliveira/agno-react

v2.3.0

Published

React hooks and pre-built UI components for Agno client

Readme

@rodrigocoliveira/agno-react

React hooks and pre-built UI components for Agno client with full TypeScript support.

Installation

npm install @rodrigocoliveira/agno-react

This 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 /ui sub-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 configuration
  • children (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 session

useAgnoSession()

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-react

Per-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-tooltip

Note: use-stick-to-bottom and streamdown were marked optional in 2.1.0 and earlier, which prevented npm install from warning when they were missing — consumers using /ui would then hit a runtime "Could not resolve …" error. As of 2.1.1 they are required peer deps and npm install reports 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 public

Publish order: This package depends on both @rodrigocoliveira/agno-types and @rodrigocoliveira/agno-client, so publish them first:

  1. @rodrigocoliveira/agno-types
  2. @rodrigocoliveira/agno-client
  3. @rodrigocoliveira/agno-react (this package)

License

MIT