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

@agno-hq/agent-os

v1.0.0

Published

Composable, embeddable React components for AgentOS — a headless chat hook, a provider for building your own layout, and drop-in chat embeds.

Readme

@agno-hq/agent-os

Composable, embeddable React components for AgentOS — a headless hook, a provider for building your own layout, and drop-in embeds for streaming any Agno agent, team, or workflow.

Formerly @agno-hq/chat-react. Upgrading from it means renaming your imports and nothing else. See Migrating.

| Subpath | Contents | | ------------------------------ | ---------------------------------------------------------------------- | | @agno-hq/agent-os | Everything below. | | @agno-hq/agent-os/core | AgnoClient, run streaming, event helpers, types. No React rendering. | | @agno-hq/agent-os/chat | useAgnoChat, ChatProvider, AgnoChat, and every building block. | | @agno-hq/agent-os/styles.css | Tokens + component styles. | | @agno-hq/agent-os/tokens.css | CSS variables only. | | @agno-hq/agent-os/fonts.css | Inter + DM Mono (optional). |

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/agent-os/chat'
import '@agno-hq/agent-os/styles.css'
import '@agno-hq/agent-os/fonts.css' // optional — Inter + DM Mono

export default function Page() {
  return <AgnoChat baseUrl="http://localhost:7777" allowFiles />
}

That's the zero-wiring embed: it discovers the agents/teams/workflows on your AgentOS and runs a full chat. It renders no header and fills its container, so it drops into a page, a card, a drawer or a modal without fighting your layout — put your own chrome in the header slot.

Support chat on every page is the same component in launcher mode:

<AgnoChat mode="launcher" baseUrl="..." launcherLabel="Chat with us" />

Compose it instead

<ChatProvider> runs the chat and shares it through context, so every component below reads it with no props:

import { ChatProvider, MessageList, ChatInput } from '@agno-hq/agent-os/chat'

;<ChatProvider baseUrl="http://localhost:7777" entity={agent}>
  <MyOwnHeader />
  <MessageList />
  <ChatInput />
</ChatProvider>

Reach for useChatContext() to drive it from your own components, or call useAgnoChat directly and render nothing of ours.

Customize it

Three independent levers:

/* 1. Tokens — palette, radii and shadows from agno-os/globals.css */
:root { --color-brand-brand: 99, 102, 241; --agno-radius-lg: 1.25rem; }

/* 2. Slots — every element is addressable by name */
<AgnoChat classNames={{ root: 'rounded-2xl', textarea: 'font-mono' }} />

/* 3. Replacement — swap any piece for your own */
<ChatWindow composer={<MyComposer />} header={<MyBar />} />

The built-in classes stay on the elements, so Tailwind utilities layer on top rather than replacing them. Import only tokens.css (or nothing) to style from scratch.

Storybook

npm run storybook runs the component gallery locally; every story streams against a scripted in-memory backend, so it exercises the real hook and the real event reducers without an AgentOS running. Pushes to main publish it to GitHub Pages via .github/workflows/storybook.yml.


Install

npm install @agno-hq/agent-os

react 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/agent-os'
import '@agno-hq/agent-os/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.

Buffered streaming

Plain text is batched so the answer reveals in smooth steps instead of one character at a time. The first text of an answer is shown immediately; after that, text accumulates for 150 ms and each flush fades in as one batch. Tool calls, media, pauses, errors and completion are never delayed: each one flushes the pending text ahead of it, so the transcript always stays in order.

<AgnoChat
  baseUrl="http://localhost:7777"
  streaming={{
    flushIntervalMs: 150,
    maxBufferEvents: 100,
    maxBufferChars: 16_384
  }}
/>

The same streaming prop works on useAgnoChat and <ChatProvider>.

| Option | Default | Effect | | ----------------- | ------- | ----------------------------------------------------------------- | | enabled | true | false delivers every event as it arrives. | | flushIntervalMs | 150 | How long text accumulates before a flush. 0 disables buffering. | | maxBufferEvents | 100 | Flush early once this many events are pending. | | maxBufferChars | 16384 | Flush early once this many characters are pending. | | animation | 400ms | How each batch fades in: { durationMs }, or false for none. |

Settings are read when a run or continuation starts, so changing the prop affects the next one. A hidden tab flushes immediately, since nothing is painting. Invalid values fall back to the defaults. DEFAULT_STREAMING_OPTIONS and StreamingOptions are exported from the root and /chat entry points.

What stays immediate:

  • onEvent receives every raw event as it arrives, unbuffered.
  • events, currentEvent and each message's event log update per batch, with every original event preserved in order.
  • Cancelling keeps the text already received. Switching sessions keeps the buffer with its run; deleting the session or unmounting discards it.
  • AgnoClient and streamRun are never buffered.

Each batch fades in through Streamdown's own animation, with every word of a batch starting together. Fenced code stays plain until the run completes, then highlights. Reduced motion turns the fade off. Pass animated in options on <Markdown> to change or disable it.

Try Streaming / Buffered in Storybook to compare the defaults, a longer buffer and per-event updates.

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/agent-os/styles.css, which carries the Agno OS design system — the same colour tokens, radii, Inter/DM Mono type scale and chat layout as the AgentOS chat page. It is dark by default; add the agno-light class on a wrapper for light mode (agno-dark is accepted too, for apps that toggle both). Every piece is exported so you can compose your own layout.

Theme it by overriding the tokens on your wrapper — they are the AgentOS ones, as R, G, B triples:

.agno-root.my-brand {
  --color-brand-brand: 255, 64, 23;
  --color-background: 17, 17, 19;
  --color-background-secondary: 39, 39, 42;
}

| Component | Purpose | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | <AgnoChat> | All-in-one widget: discovery, selector, chat, optional per-run event feed. | | <ChatWindow chat={chat}> | Full chat surface built from a useAgnoChat result. | | <ChatLauncher> | Floating support-widget shell: a corner bubble that opens any chat surface. | | <QuickPrompts> | Suggested-prompt chips. | | <MessageList> | Transcript with live status + footer slot. Scrolls to the end when a session opens or a message is sent, never mid-answer. | | <Message> | A single message: content, behind-the-scenes panel, media, citations, follow-ups, action row. | | <MessageActions> | The icon row under a finished answer — copy, plus whatever the product adds. | | <CopyButton> | The copy control, with the "copied" flash and clipboard fallbacks — the one code blocks and answers use. | | <BehindTheScenes> | One collapsible timeline over a run's reasoning, tools, member turns, steps and events — doubles as the live status line. | | <ChatInput> | Multiline input with file attach, send, and stop; opens up for @mentions, context chips and a custom textarea. | | <ToolCalls> | Collapsible tool-call cards (name, args, result, status). | | <Reasoning> | Collapsible reasoning-steps panel. | | <Citations> | The sources behind an answer, as linked cards. | | <Followups> | "Related questions" — the follow-up prompts an agent suggests after its answer. | | <Multimedia> | Images, video, and audio attachments. | | <StatusIndicator> | Animated "what is it doing now" line. | | <EventLog> | Developer feed of raw run events — render it yourself from chat.events. | | <HumanInput> | Human-in-the-loop panel (confirm / reject / input). | | <EntitySelector> | Dropdown of agents, teams, and workflows. | | <SessionList> | Sidebar of past sessions — click to load; each row's menu renames or deletes it. | | <Markdown> | Markdown, rendered by Streamdown — GFM, and repair for half-written text mid-stream. |

Inline, or a floating support widget

<AgnoChat> renders inline by default — a panel wherever you put it, sized by width and height:

<AgnoChat baseUrl="http://localhost:7777" width={720} height={560} />

mode="launcher" turns it into the support-chat layout instead: a bubble pinned to a corner of the viewport that opens the chat. Mount it once, anywhere in your app — it renders into document.body, so no wrapper can clip it.

<AgnoChat
  baseUrl="http://localhost:7777"
  mode="launcher"
  position="bottom-right"
  launcherLabel="Chat with us"
  width={400}
  height={620}
/>

| Prop | Default | Description | | ----------------------- | ---------------- | -------------------------------------------------------------------- | | mode | 'inline' | 'inline' renders in place; 'launcher' pins a bubble to a corner. | | position | 'bottom-right' | Corner to pin to: also bottom-left, top-right, top-left. | | offset | 24 | Distance from both edges of that corner. Number means px. | | panel | 'popover' | How far it opens: a corner popover, or 'fullscreen' over the page. | | width / height | 400 / 620 | Size of the popover (and of the inline panel). | | launcherLabel | — | Text beside the bubble glyph. Icon-only without it. | | launcherIcon | speech bubble | Replaces the default glyph. | | defaultOpen | false | Open on first render. | | open / onOpenChange | — | Control the open state yourself (e.g. from your own "Help" button). |

A 'fullscreen' panel covers the page and closes from an X in the chat header; a popover closes from the bubble, which becomes an X. Escape closes either, and below 480px wide a popover goes full-screen on its own. The conversation is kept across close/open — a run that is still streaming keeps streaming.

To give your own layout the same behaviour, wrap it in <ChatLauncher>:

<ChatLauncher position="bottom-left" panel="fullscreen" label="Support">
  <ChatWindow chat={chat} />
</ChatLauncher>

Errors

A failed run leaves its message on chat.error, but nothing is rendered by default — raw backend errors are long, and they crowd a narrow window. Opt in with showErrors on <AgnoChat> or <ChatWindow>:

<AgnoChat baseUrl="http://localhost:7777" showErrors />

Or render it wherever suits your layout — a toast, say:

{
  chat.error && <Toast>{chat.error}</Toast>
}

Quick prompts

quickPrompts offers suggested prompts while the transcript is empty. Clicking one sends it. A chip is either the prompt itself, or a short label that sends something longer:

<AgnoChat
  baseUrl="http://localhost:7777"
  quickPrompts={[
    'What can you do?',
    { label: 'Pricing', prompt: 'How does pricing work?' },
    { label: 'Talk to a human', prompt: 'I would like to talk to a human.' }
  ]}
/>

<ChatWindow> takes the same prop, plus onQuickPrompt to do something other than send the text. Render <QuickPrompts> yourself to place the chips elsewhere.

Building on the composer

<ChatInput> is the AgentOS dock: an auto-growing textarea, attach, send and stop. compact trades the dock for one row — the field with the buttons beside it — for a launcher, a sidebar, anywhere narrow; compactInput on <AgnoChat> and <ChatWindow> switches their built-in composer to it:

<AgnoChat baseUrl="http://localhost:7777" mode="launcher" compactInput />
<ChatInput compact placeholder="Send a message" />

When a product needs more from the composer — an @mention menu, context chips, a highlighted draft — it opens up rather than being replaced:

<ChatInput
  value={draft} // own the draft
  onValueChange={setDraft}
  textareaRef={inputRef}
  textareaProps={{
    // reach the textarea itself
    onKeyDown: (e) => {
      if (menu.handles(e)) e.preventDefault()
    }, // claims the key
    role: 'combobox',
    'aria-expanded': menuOpen
  }}
  renderTextarea={(props) => <HighlightedTextarea {...props} />} // or swap it
  above={menuOpen && <MentionMenu />} // inside the dock, over the field
  below={contextError && <p>{contextError}</p>}
  allowFiles
  accept=".md,.py,image/*" // dropped files obey this too
  maxFiles={8}
  maxFileSize={20 * 1024 * 1024}
  onSend={async (text, files) => {
    const ok = await prepare(text)
    if (!ok) return false // keep the draft
    chat.sendMessage(text, files ? { files } : undefined)
  }}
/>

A key handler in textareaProps runs before Enter-to-send; preventDefault() there claims the key. Files can be dropped anywhere on the dock; the first reason one was refused shows under the attachment chips (admitFiles is the same check, exported).

Links

Every link the components render — in an answer, on a [1] chip, on a source card — is a plain <a target="_blank">. Inside an app with a router, hand the provider a linkComponent and in-app URLs stay client-side:

<ChatProvider linkComponent={DocsLink}>…</ChatProvider>
<AgnoChat linkComponent={DocsLink} />

It receives href, children and the anchor attributes the component would have set, and decides how to navigate — nothing sets target for it, so a link opens in the same page unless the component says otherwise. The usual shape routes your own origin through the router and pops everything else out:

import { Link } from 'react-router-dom'
import type { LinkComponent } from '@agno-hq/agent-os/chat'

const DocsLink: LinkComponent = ({ href, children, ...rest }) => {
  const url = new URL(href, window.location.href)
  return url.origin === window.location.origin ? (
    <Link to={url.pathname + url.search + url.hash} {...rest}>
      {children}
    </Link>
  ) : (
    <a href={href} target="_blank" rel="noreferrer noopener" {...rest}>
      {children}
    </a>
  )
}

Sources with no URL — retrieved knowledge-base chunks — are never links; their [1] chip points at the card below the answer.

Run events

Every run keeps the events that produced it on its own message (message.events) — live, and for runs restored from session history when the agent stores them. <BehindTheScenes> builds its steps from those events the way the AgentOS chat does: content deltas, model-request and reasoning-delta events are dropped, started/completed pairs collapse into one step, and run lifecycle events read as "Run Started" / "Run Completed" with the reported duration. A message with no events falls back to what it does carry (tools, reasoning, member turns, workflow steps).

The raw feed is not part of that panel. chat.events holds the latest run's events, and every message keeps its own in message.events — render either with <EventLog> wherever you want it:

<EventLog events={chat.events} maxHeight={240} />

Citations, sources and link previews

Whatever a run cites — knowledge-base chunks in references, browsed links in citations.urls — is flattened into one numbered list and rendered as cards under the answer: the site's favicon and the title, linking to the source, with the rest behind a "+N more" card past the first three. A [1] in the answer text becomes a numbered disc pointing at card one, and hovering either the disc or a link opens a preview with the URL and the cited passage:

<Message message={message} />           // markers, cards and previews, no props
<Message message={message} hideSources />   // just the answer

The hostname and favicon (/favicon.ico, then /favicon.svg) come from the URL itself, and the title and passage from the citation payload, so previews need no network of their own. To fill in OpenGraph metadata, hand the provider a resolveLinkPreview — the browser can't read another origin's meta tags, so back it with an endpoint of your own. It is called once per URL, on first hover:

<ChatProvider
  baseUrl="http://localhost:7777"
  resolveLinkPreview={async (url) => {
    const res = await fetch(`/api/og?url=${encodeURIComponent(url)}`)
    return res.ok ? res.json() : null // { title, description, image, siteName, favicon }
  }}
>
  <ChatWindow />
</ChatProvider>

Plenty of runs carry no references or citations at all — the model cites in the answer itself, as numbered links, and lists the titles at the end:

Call `Agent.run()` in production. [1](https://docs.agno.com/agents/building-agents)

Sources: [Building Agents](https://docs.agno.com/agents/building-agents)

That reads the same way: the numbered link becomes the chip, the labelled link names the card, and the numbering stays exactly as the model wrote it. Links in prose are left alone — only numbered ones count as citations.

Rendering sources yourself? collectSources(references, citations) and sourcesFromMarkdown(content) are the same numbering the components use, and <SourceCard>, <LinkPreviewCard> and <HoverPreview> are exported to build with.

Follow-ups

An agent built with followups=True suggests what to ask next once it has answered. They sit above the composer as "Related questions", one line each — the full text is on hover — and clicking one sends it:

<AgnoChat baseUrl="http://localhost:7777" />                          // on by default
<AgnoChat baseUrl="http://localhost:7777" hideFollowups />            // off
<AgnoChat baseUrl="http://localhost:7777" followupsTitle="Ask next" /> // your own heading

Only the latest answer offers them; earlier ones have been answered by the conversation moving on. They arrive as a FollowupsCompleted event and are stored on the run, so a reloaded session still shows them. To put them under the answer instead, pass <Message onFollowup={chat.sendMessage}>; to place them anywhere else, render <Followups> yourself from message.followupslines lets them wrap instead of clamping.

Markdown

Answers are rendered by Streamdown, which is built for text that arrives a token at a time: mid-stream it repairs the unterminated bold, the half-written link and the unclosed fence instead of showing you the raw characters. GFM comes with it — tables, task lists, strikethrough — along with a sanitising pass over the HTML.

Streamdown styles itself with Tailwind utility classes. Every element is mapped back onto this package's own agno-md-* classes, so Tailwind is not required and the shipped stylesheet and tokens still govern the look.

Fenced code blocks get a header naming the language, a line-number gutter and syntax highlighting by Shiki. The highlighter loads on the first fenced block, not before, and grammars load one at a time as languages appear; a language Shiki doesn't know renders plain. Colours follow the theme class like everything else. --agno-code-max-height (default 32rem) caps a block before it scrolls.

The header also holds a copy button. Turn it off per-renderer or for a whole tree:

<Markdown content={answer} codeCopy={false} />
<ChatProvider codeCopy={false}>…</ChatProvider>
<AgnoChat codeCopy={false} />

Message actions

A finished answer carries an action row — the AgentOS strip of small icon buttons under the text, shown when the message is pointed at. The package ships copy; the row is a slot for the rest:

<Message
  message={m}
  actions={<>
    <button className="agno-msg__action" onClick={() => regenerate(m)} aria-label="Regenerate">
      <RotateCw size={14} />
    </button>
    <button className="agno-msg__action" onClick={() => rate(m, 'up')} aria-label="Good answer">
      <ThumbsUp size={14} />
    </button>
  </>}
/>
<Message message={m} hideActions />          // no row at all

<MessageActions message={m}> is the row on its own, for a transcript you render yourself, and <CopyButton text={…}> is the control inside it — the same one a code block's header uses, so every copy affordance flashes the same tick and falls back the same way when the Clipboard API is unavailable.

Prefer a different renderer? renderMarkdown replaces it entirely:

import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

;<ChatWindow
  chat={chat}
  renderMarkdown={(c) => (
    <ReactMarkdown remarkPlugins={[remarkGfm]}>{c}</ReactMarkdown>
  )}
/>

Note that streamdown is ESM-only, as is the remark/rehype ecosystem under it. Bundlers resolve that fine; a bare require() of the CJS entry needs Node 22.12+.


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 continue

Agents 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 first page (20 sessions)
await chat.loadMoreSessions() // append the next page, while chat.hasMoreSessions
await chat.loadSession(sessionId) // load a transcript into the chat
await chat.renameSession(sessionId, 'Refund for order #4471')
await chat.deleteSession(sessionId)

The list updates optimistically, as the AgentOS chat does: the moment a run reports its session id, that session moves to the top of chat.sessions — created on the spot, named after the message that started it, if the backend just made it — rather than only appearing after the next refreshSessions(). A later refresh keeps the in-flight session even if the backend has not persisted it yet.

Render them with <SessionList>:

<SessionList
  sessions={chat.sessions}
  activeSessionId={chat.sessionId}
  streamingSessionIds={chat.streamingSessionIds}
  loading={chat.sessionsLoading}
  onSelect={chat.loadSession}
  hasMore={chat.hasMoreSessions}
  loadingMore={chat.sessionsLoadingMore}
  onLoadMore={chat.loadMoreSessions}
  onRename={chat.renameSession}
  onDelete={chat.deleteSession}
  onNew={chat.reset}
/>

streamingSessionIds marks the sessions with a run in flight — each shows a spinner in place of the delete button.

Switching session does not stop the run

Selecting another session (or starting a new chat) while a run is streaming parks that run instead of cancelling it, as the AgentOS chat does: it keeps streaming in the background, stays listed in chat.streamingSessionIds, and loadSession restores it — mid-stream or finished — when you switch back, rather than refetching a transcript that is still being written. Only chat.cancel() stops a run.

The all-in-one widget shows this sidebar with showSessions:

<AgnoChat baseUrl="http://localhost:7777" showSessions />

Lower-level API

import { AgnoClient, streamRun } from '@agno-hq/agent-os'

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 the widget, the floating launcher, every component, and the hook (with a live status panel + event log). It imports the library from source.

cd example
npm install
npm run dev

Then 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-built client) to send an Authorization header on every request.
  • SessionssessionId is captured automatically on the first run. To restore history, call client.getSessionRuns(...) and map runs into ChatMessage[], then chat.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-react-components.git
cd agno-react-components
npm install
npm test                   # Node test runner
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 tests, typecheck, and build automatically, so npm publish always ships a fresh dist/.


License

MIT © Agno