@adecore/agents-react
React views for agent chats: the thread, the composer, approvals and questions, the agent CLIs and their accounts, and what they cost. It runs in the page and talks to a host of chats, such as @adecore/agents, over the requests and events of @adecore/agent-contracts. It starts no process and registers no channel.
import { Timeline } from '@adecore/agents-react/chat/ui/Timeline';
import { Composer } from '@adecore/agents-react/chat/ui/Composer';import { useEffect } from 'react';
import { AgentIcon } from '@adecore/agents-react/agents/AgentIcon';
import { useSubagentTrail } from '@adecore/agents-react/chat/subagent-view';
import { AccountPill } from '@adecore/agents-react/chat/ui/AccountPill';
import { Composer } from '@adecore/agents-react/chat/ui/Composer';
import { LimitPill } from '@adecore/agents-react/chat/ui/LimitState';
import { SubagentBreadcrumb, SubagentTitleCrumb } from '@adecore/agents-react/chat/ui/SubagentControls';
import { SubagentTimeline } from '@adecore/agents-react/chat/ui/SubagentTimeline';
import { Timeline } from '@adecore/agents-react/chat/ui/Timeline';
import { useChatScope } from '@adecore/agents-react/scope';
import { useChatRow } from '@adecore/agents-react/state/chats';
import { AgentDemo } from '../shared/agents.tsx';
import { DEMO_CHAT } from '../shared/agents-host.ts';
function Chat() {
const scope = useChatScope();
const info = useChatRow(DEMO_CHAT, (row) => row?.info);
const { trail } = useSubagentTrail(DEMO_CHAT);
const open = trail.at(-1);
useEffect(() => {
void scope.chats.open(DEMO_CHAT, {});
return () => void scope.chats.detach(DEMO_CHAT);
}, [scope]);
return (
<div className="flex h-[640px] w-full flex-col overflow-hidden rounded-lg border border-border bg-surface">
<header className="flex h-10 shrink-0 items-center gap-2 border-b border-border px-3">
<AgentIcon kind={info?.provider ?? 'claude'} />
<SubagentTitleCrumb chatId={DEMO_CHAT} className="text-sm font-medium">
<span className="truncate text-sm font-medium">Retry for fetchJson</span>
</SubagentTitleCrumb>
<SubagentBreadcrumb chatId={DEMO_CHAT} />
<span className="ml-auto flex items-center gap-1.5">
<LimitPill chatId={DEMO_CHAT} />
<AccountPill chatId={DEMO_CHAT} />
</span>
</header>
<Timeline
chatId={DEMO_CHAT}
overlay={open && <SubagentTimeline key={open.toolUseId} chatId={DEMO_CHAT} toolUseId={open.toolUseId} />}
composer={
info && (
<Composer
chatId={DEMO_CHAT}
info={info}
focused={false}
disabled={false}
providerFixed={false}
onSend={(text, extras) => void scope.chats.send(DEMO_CHAT, text, extras)}
onRetarget={(provider, selection) => scope.chats.retarget(DEMO_CHAT, provider, selection)}
/>
)
}
/>
</div>
);
}
/* Send a message that mentions a check to see an approval; any other message gets a reply. */
export default function ChatDemo() {
return (
<AgentDemo>
<Chat />
</AgentDemo>
);
}The demos on these pages run on a host in memory: two CLIs, three accounts, a chat two turns in, usage for the last months. Send a message and it answers a word at a time; mention a check or a test and it asks for permission first. Open the helper agent's row to read its own conversation. Nothing leaves the page.
What is in it
- Chat:
Timelinedraws the thread andComposerwrites to it, with prompts for the approvals and questions a chat waits on. Messages and code, diffs, subagents, limits and smaller parts are there for a layout of your own. The pages an agent publishes appear in the thread as visuals. - Providers:
ProvidersPaneis the settings of the CLIs and their accounts; marks and the model picker are its parts. - Usage:
UsagePageshows what the CLIs cost over a period, with its charts and tiles and the plan limits.
How it fits together
your app setChatHost(...) words, storage, files, navigation
|
scope ChatScopeContext one per host of chats
| transport requests and events
| ChatClient writes them into the stores
|
views Timeline, Composer, ... read the stores of their scopeThe views read a chat from a store, never from the wire. A ChatClient fills that store from a ChatTransport, the one object that knows how to reach the host. A ChatScope bundles the two with an id and puts them in context, so an app that talks to two hosts renders a scope for each and their threads never mix. What only the app can decide, such as where a file opens or whether a stop needs a confirmation, it hands over once with setChatHost.
Entry points
There is no root import. Every module has its own subpath, @adecore/agents-react/<path>, without an extension; the module reference lists all of them. DiffPool, EditDiff and UnifiedDiff are default exports, everything else is named. Two entries are assets: @adecore/agents-react/theme.css and the words under @adecore/agents-react/locales/<language>/<namespace>.json.
Where to go next
- Getting started installs the package, its CSS and its words, and renders a first chat.
- Host adapters lists what
setChatHosttakes and what each part does without it. - Chat client and state covers opening, sending, reconnecting and reading the stores.
- Persistence covers drafts, preferences and their storage keys.
- Testing shows a host in memory like the one these demos use.