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

@inharness-ai/agent-chat

v0.2.0

Published

React chat UI kit for @inharness-ai/agent-adapters — drop-in components and hooks for AI agent conversations

Readme

@inharness-ai/agent-chat

React chat UI kit for @inharness-ai/agent-adapters. Drop-in components and hooks for building AI agent conversations with streaming support.

Works with all agent architectures: Claude Code, Codex, OpenCode, Gemini.

Features

  • Drop-in <AgentChat /> — full chat UI in one component
  • Individual hooksuseAgentChat(), useMessageReducer(), useEventStream() for custom UI
  • Streaming — real-time text deltas, thinking blocks, tool cards, subagent panels
  • Thread persistence — JSON file-based conversation history with auto-save
  • Architecture/model selector — switch between agents and models from the UI
  • Themeable — CSS custom properties, override with plain CSS, light + dark themes
  • Node backend — Express handlers that bridge HTTP/SSE to agent-adapters

Try it instantly (zero config)

Run the bundled server + UI in any directory — no install, no setup:

ANTHROPIC_API_KEY=sk-... npx @inharness-ai/agent-chat basic

Then open the printed URL (defaults to http://localhost:3001). Threads are saved as JSON files under ./threads in the current directory. A .env file in the current directory is loaded automatically (Node 20.6+).

Pick the architecture (claude-code, gemini, codex, …) from the in-UI config bar — set the matching env var (ANTHROPIC_API_KEY, GEMINI_API_KEY, …) for whichever you want to use.

# Common options
npx @inharness-ai/agent-chat basic --port 4000
npx @inharness-ai/agent-chat basic --threads-dir ./.chat-threads
npx @inharness-ai/agent-chat basic --no-open    # don't auto-open browser

For embedding the components into your own app, follow the Quick Start below.

Quick Start

1. Install

npm install @inharness-ai/agent-chat react react-dom

@inharness-ai/agent-adapters, express, and cors are pulled in as dependencies; react / react-dom are peer-installed.

2. Server

// server.ts
import express from 'express';
import cors from 'cors';
import { createChatHandler } from '@inharness-ai/agent-chat/server';

const handler = createChatHandler({
  architectures: {
    'claude-code': {
      models: ['claude-sonnet-4-20250514', 'claude-opus-4-20250514'],
      default: 'claude-sonnet-4-20250514',
    },
    'codex': {
      models: ['o4-mini', 'o3'],
      default: 'o4-mini',
    },
    'gemini': {
      models: ['gemini-2.5-pro', 'gemini-2.5-flash'],
      default: 'gemini-2.5-pro',
    },
  },
  defaultArchitecture: 'claude-code',
  systemPrompt: 'You are a helpful assistant.',
  threadsDir: './threads',
});

const app = express();
app.use(cors());
app.use(express.json());

// Chat endpoints
app.post('/api/chat', handler.handleChat);
app.post('/api/chat/abort', handler.handleAbort);
app.get('/api/config', handler.handleConfig);

// Thread endpoints
app.get('/api/threads', handler.handleListThreads);
app.post('/api/threads', handler.handleCreateThread);
app.get('/api/threads/:id', handler.handleGetThread);
app.delete('/api/threads/:id', handler.handleDeleteThread);
app.patch('/api/threads/:id', handler.handleUpdateThread);

app.listen(3001, () => console.log('Server running on http://localhost:3001'));

3. Client — Drop-in

import { AgentChat } from '@inharness-ai/agent-chat';
import '@inharness-ai/agent-chat/styles';

function App() {
  return (
    <AgentChat
      serverUrl="http://localhost:3001"
      theme="dark"
      showConfigBar={true}
      showThreadList={true}
      showUsage={true}
      batchTools={false}
    />
  );
}

<AgentChat /> props

| Prop | Type | Default | Description | |---|---|---|---| | serverUrl | string | — | Base URL of the Express server running createChatHandler. | | theme | 'light' \| 'dark' | 'light' | Sets data-ac-theme on the root element. | | onThemeChange | (theme) => void | — | If provided, shows a theme toggle button in the UI. | | className | string | — | CSS class forwarded to the root element. | | showConfigBar | boolean | true | Render the architecture/model dropdowns and advanced-options drawer. | | showThreadList | boolean | true | Render the thread sidebar. | | showUsage | boolean | false | Render the UsageDisplay footer (tokens + context window). | | batchTools | boolean | false | Collapse consecutive tool calls of the same category into a single ToolBatchBlock. |

3. Client — Custom UI with Hooks

import { useAgentChat } from '@inharness-ai/agent-chat';
import '@inharness-ai/agent-chat/styles';

function MyChat() {
  const {
    // Conversation state
    messages,
    isStreaming,
    error,
    usage,
    sessionId,
    contextWindow,
    currentTodoItems,

    // Architecture & model
    config,
    configLoading,
    architecture,
    model,
    setArchitecture,
    setModel,

    // Advanced options (forwarded to the server on each turn)
    cwd, setCwd, activeCwd, defaultCwd,
    systemPrompt, setSystemPrompt,
    maxTurns, setMaxTurns,
    architectureConfig, setArchitectureConfig, architectureOptions,
    planMode, setPlanMode,

    // Threads
    threads,
    activeThreadId,
    createThread,
    loadThread,
    deleteThread,
    renameThread,

    // Actions
    sendMessage,
    abort,
  } = useAgentChat({ serverUrl: 'http://localhost:3001' });

  return (
    <div>
      {/* Build your own UI using the state and actions above */}
      <select value={architecture} onChange={e => setArchitecture(e.target.value)}>
        {config && Object.keys(config.architectures).map(a => (
          <option key={a} value={a}>{a}</option>
        ))}
      </select>

      {messages.map(msg => (
        <div key={msg.id}>
          <strong>{msg.role}:</strong>
          {msg.blocks.map((block, i) => (
            block.type === 'text' && <p key={i}>{block.text}</p>
          ))}
        </div>
      ))}

      <input
        onKeyDown={e => e.key === 'Enter' && sendMessage(e.currentTarget.value)}
        disabled={isStreaming}
      />
      {isStreaming && <button onClick={abort}>Stop</button>}
    </div>
  );
}

Components

All components use data-ac attributes for styling. You can use them individually:

| Component | Description | |---|---| | <AgentChat /> | Full drop-in chat (composes everything below) | | <ChatContainer /> | Scrollable message area with auto-scroll | | <MessageList /> | Renders a list of ChatMessage objects | | <ChatMessage /> | Single message (user or assistant) | | <AssistantContent /> | Renders content blocks (text, tools, thinking, etc.) | | <UserContent /> | Renders user message | | <TextBlock /> | Markdown-rendered text with syntax highlighting | | <CodeBlock /> | Code block with language label and copy button | | <ThinkingBlock /> | Collapsible reasoning/thinking block | | <ToolUseBlock /> | Tool invocation card with collapsible input | | <ToolResultBlock /> | Tool result with collapsible output | | <ToolBatchBlock /> | Groups consecutive same-category tool calls into a summary block | | <TodoListBlock /> | Renders a todo-list content block from the agent | | <CurrentTodoList /> | Sticky header showing the thread's active todo list | | <ImageBlock /> | Base64 or URL image | | <SubagentPanel /> | Nested subagent container with progress | | <ConfigBar /> | Architecture and model dropdowns | | <AdvancedOptions /> | Drawer for cwd, systemPrompt, maxTurns, architecture-specific options, plan mode | | <ThreadList /> | Sidebar with conversation list | | <ThreadItem /> | Single thread entry with rename/delete | | <InputArea /> | Text input with send/abort buttons | | <ErrorDisplay /> | Error banner | | <LoadingIndicator /> | Animated loading dots | | <UsageDisplay /> | Token usage (input/output) |

Hooks

| Hook | Description | |---|---| | useAgentChat(config) | Top-level hook — composes all others. Returns messages, config, threads, and actions. Forwards endpoints to useEventStream / useThreads. | | useMessageReducer(arch, model) | Pure state machine for chat messages. Processes WireEvent stream into ChatMessage[]. | | useEventStream(options) | Low-level SSE connection. Returns { startStream, joinStream, abort, disconnect }. abort() stops the turn (closes the local stream and tells the server to abort via POST /api/chat/abort); disconnect() only closes the local stream so the server keeps streaming and persisting — reattach later with joinStream(threadId). Endpoints configurable via options.endpoints (StreamEndpoints). | | useAgentConfig(serverUrl) | Fetches GET /api/config, tracks current architecture and model. | | useThreads(options) | CRUD operations on conversation threads. Endpoints configurable via options.endpoints (ThreadsEndpoints). |

Theming

The default theme uses CSS custom properties. Override any --ac-* variable:

/* Change accent color */
:root {
  --ac-accent: #2563eb;
  --ac-accent-hover: #1d4ed8;
}

/* Custom assistant message background */
[data-ac="message"][data-role="assistant"] {
  background: #f0f9ff;
}

/* Hide thinking blocks */
[data-ac="thinking"] {
  display: none;
}

/* Wider chat area */
:root {
  --ac-chat-max-width: 1200px;
}

Switch between light and dark themes:

<AgentChat serverUrl="..." theme="dark" />

Available CSS Variables

| Variable | Default (light) | Description | |---|---|---| | --ac-chat-max-width | 900px | Max width of chat area | | --ac-chat-height | 100vh | Height of chat container | | --ac-sidebar-width | 280px | Thread list sidebar width | | --ac-bg-primary | #ffffff | Main background | | --ac-bg-assistant | #f4f4f6 | Assistant message background | | --ac-bg-user | #e8e0ff | User message background | | --ac-bg-tool | #f5f3ff | Tool card background | | --ac-bg-thinking | #fffbeb | Thinking block background | | --ac-bg-code | #1e1e2e | Code block background | | --ac-accent | #7c3aed | Primary accent color | | --ac-font-family | system-ui, ... | Base font | | --ac-font-mono | SF Mono, ... | Code font | | --ac-border-radius | 8px | Border radius |

See variables.css for the full list (50+ variables).

Server API

createChatHandler(config)

Creates an object with Express-compatible request handlers.

interface ChatHandlerConfig {
  architectures: Record<string, { models: string[]; default: string }>;
  defaultArchitecture: string;
  systemPrompt: string;
  maxConcurrentRequests?: number;  // default: 10
  threadsDir?: string;             // default: './threads'
  cwd?: string;                    // working directory for agents
  onEvent?: (event, requestId) => void;  // event hook for logging
  logger?: Logger;                 // sink for non-fatal errors; defaults to console.warn in dev
}

Endpoints

| Method | Path | Description | |---|---|---| | POST | /api/chat | Start a chat turn (returns SSE stream) | | POST | /api/chat/abort | Abort an active stream by requestId | | GET | /api/chat/stream/:threadId | Join an in-flight stream live (used after F5 / thread switch) | | GET | /api/config | Available architectures and models | | GET | /api/threads | List all threads (metadata only) | | POST | /api/threads | Create a new thread | | GET | /api/threads/:id | Get thread with full message history | | DELETE | /api/threads/:id | Delete a thread | | PATCH | /api/threads/:id | Rename a thread |

These paths are the defaults the client hooks call. They can be overridden per client via endpoints (see Custom endpoints) — the server side is unchanged either way.

Custom endpoints

Both useEventStream and useThreads accept an endpoints option to override the default paths. Useful when your backend mounts the chat surface under a different prefix, or your routing scheme doesn't match the canonical layout. Omit endpoints to fall back to defaults — no breaking change for existing consumers.

import { useEventStream, useThreads } from '@inharness-ai/agent-chat';

useEventStream({
  serverUrl: 'http://localhost:3001',
  onEvent, onError,
  endpoints: {
    chat: '/v2/chat/start',                                      // POST start
    abort: '/v2/chat/stop',                                      // POST { requestId }
    streamByThread: (id) => `/v2/chat/live/${encodeURIComponent(id)}`, // GET join
  },
});

useThreads({
  serverUrl: 'http://localhost:3001',
  onThreadLoaded,
  endpoints: {
    threads: '/v2/threads',
    threadById: (id) => `/v2/threads/${encodeURIComponent(id)}`,
  },
});

useAgentChat forwards an outer endpoints object to both:

useAgentChat({
  serverUrl: 'http://localhost:3001',
  endpoints: {
    stream: { chat: '/v2/chat/start', abort: '/v2/chat/stop' },
    threads: { threads: '/v2/threads' },
  },
});

Chat Request Body

{
  "prompt": "Hello",
  "threadId": "optional-thread-id",
  "architecture": "claude-code",
  "model": "claude-sonnet-4-20250514",
  "sessionId": "optional-session-id-for-resumption",
  "systemPrompt": "optional per-turn system prompt override",
  "maxTurns": 20,
  "allowedTools": ["Read", "Grep"],
  "architectureConfig": { "extra": "options" },
  "cwd": "/absolute/path/for/agent",
  "planMode": false
}

| Field | Required | Description | |---|---|---| | prompt | ✓ | User message text. | | threadId | — | Omit to auto-create a new thread. | | architecture | — | Overrides defaultArchitecture for this turn. | | model | — | Overrides the architecture's default model. | | sessionId | — | Resume an existing agent session (adapter-specific). | | systemPrompt | — | Per-turn override of the handler's default system prompt. | | maxTurns | — | Cap on agent turns for this request. | | allowedTools | — | Allow-list of tool names the agent may call. | | architectureConfig | — | Free-form options passed to the architecture adapter (mirrors ArchOption choices). | | cwd | — | Working directory for the agent for this turn (falls back to handler default). | | planMode | — | If true, runs the agent in read-only "plan mode". |

SSE Events

The chat endpoint streams events in SSE format. Each event mirrors a UnifiedEvent from agent-adapters (the full union is WireEvent in src/server/protocol.ts):

event: connected
data: {"requestId":"...","threadId":"..."}

event: text_delta
data: {"type":"text_delta","text":"Hello","isSubagent":false}

event: thinking
data: {"type":"thinking","text":"...","isSubagent":false,"replace":false}

event: tool_use
data: {"type":"tool_use","toolName":"Read","toolUseId":"...","input":{...},"isSubagent":false}

event: tool_result
data: {"type":"tool_result","toolUseId":"...","summary":"...","isSubagent":false}

event: todo_list_updated
data: {"type":"todo_list_updated","items":[...],"source":"model-tool","isSubagent":false}

event: assistant_message
data: {"type":"assistant_message","message":{"role":"assistant","content":[...],"timestamp":"...","usage":{...}}}

event: subagent_started
data: {"type":"subagent_started","taskId":"...","description":"...","toolUseId":"..."}

event: subagent_progress
data: {"type":"subagent_progress","taskId":"...","description":"...","lastToolName":"Read"}

event: subagent_completed
data: {"type":"subagent_completed","taskId":"...","status":"success","summary":"..."}

event: result
data: {"type":"result","output":"...","usage":{"inputTokens":150,"outputTokens":42},"sessionId":"..."}

event: error
data: {"type":"error","error":"...","code":"..."}

event: flush
data: {"type":"flush"}

event: done
data: {}

Subagent-scoped events (text_delta, thinking, tool_use, tool_result, todo_list_updated) include a subagentTaskId when they belong to a specific subagent run, used for routing into the matching <SubagentPanel />.

Development

Prerequisites

  • Node.js >= 20 (the basic CLI uses process.loadEnvFile, available in 20.6+)
  • The @inharness-ai/agent-adapters repo cloned as a sibling directory (../agent-adapters)

Setup

npm install

Build

npm run build        # one-off production build (tsup + Vite for the CLI UI)
npm run build:ui     # rebuild only the bundled CLI UI (Vite)
npm run dev          # rebuild library on file changes (tsup --watch)

Typecheck

npm run typecheck    # tsc --noEmit

Tests

npm test             # vitest run (single run)
npx vitest           # vitest in watch mode

Running the example app

The examples/basic/ directory contains a working server + React UI:

cd examples/basic
npm install
npm run dev

This starts:

  • Backend on http://localhost:3001 (Express + createChatHandler)
  • UI on http://localhost:5173 (Vite + React)

Open http://localhost:5173 in the browser to see the chat.

You can also run them separately:

npm run dev:server   # only the Express backend
npm run dev:client   # only the Vite frontend

Project structure

src/
├── components/    # React UI components (<AgentChat />, <MessageList />, etc.)
├── hooks/         # React hooks (useAgentChat, useEventStream, etc.)
├── server/        # Express request handlers + session/thread management
├── cli/           # `npx @inharness-ai/agent-chat basic` — bin entry, commands,
│                  # and a self-contained Vite-built UI (src/cli/web/)
├── styles/        # CSS (variables, component styles)
├── utils/         # Tool-batching + tool-category helpers
├── types.ts       # Shared TypeScript types
└── index.ts       # Client entry point

License

MIT