@economic/agents-react
v1.4.1
Published
React hooks for connecting to agents built with [`@economic/agents`](../agents/README.md). They wrap the Cloudflare `agents` and `@cloudflare/ai-chat` hooks and add typed connection state, message feedback, and the per-user multi-chat model used by `Assis
Keywords
Readme
@economic/agents-react
React hooks for connecting to agents built with @economic/agents. They wrap the Cloudflare agents and @cloudflare/ai-chat hooks and add typed connection state, message feedback, and the per-user multi-chat model used by Assistant.
Install
npm install @economic/agents-reactReact 19 is a peer dependency.
Which hook do I use?
The package exports three hooks, layered on top of each other:
useAgent— the thin connection primitive both build on. Reach for it only when you need raw access to the connection.useChat— connects directly to a single chat agent by name. Use it when you address one conversation yourself (e.g. one chat per route) rather than going through anAssistant.useAssistant— the high-level hook for the v2 model: one assistant per user, many chats. It manages the chat list (create / open / delete), connects to the right chat facet, and exposes the active chat. Use this when your server uses anAssistant.
useAgent
The connection primitive useChat and useAssistant are built on. Connects to an agent by name and exposes its connection and call for invoking @callable methods.
import { useAgent } from "@economic/agents-react";
const agent = useAgent({
host: "localhost:8787",
agentName: "SupportAgent",
name: "support",
actorId: userId, // when set, the DO name becomes `${actorId}:${name}`
authToken,
});
await agent.call("checkOrder", ["1234"]);
console.log(agent.state?.status);useChat
Connects to a single chat agent by name — no Assistant needed. Returns the connection, the message interface, and message-feedback helpers.
import { useChat } from "@economic/agents-react";
const { status, chat, submitMessageFeedback, getMessageFeedback } = useChat({
host: "localhost:8787",
agentName: "MyChatAgent",
name: `${userId}:${chatId}`,
authToken,
toolContext: { locale: "en-DK" },
});
// send a message
chat.sendMessage({ role: "user", parts: [{ type: "text", text: "Hello" }] });
// rate a message (1 = up, -1 = down) and read all feedback
await submitMessageFeedback(messageId, 1, "Helpful!");
const feedback = await getMessageFeedback();Returns
| Field | Type | Description |
| ----------------------- | ------------------------------------------------ | ------------------------------------------------------------- |
| status | AgentConnectionStatus | Connection status. |
| agent | connection object | The underlying useAgent connection. |
| chat | useAgentChat result | messages, sendMessage, setMessages, status, stop, … |
| submitMessageFeedback | (id, rating, comment?) => Promise<void> | Submit thumbs up/down feedback for a message. |
| getMessageFeedback | () => Promise<Record<string, MessageFeedback>> | All feedback for the chat, keyed by message id. |
useAssistant
For the per-user, many-chats model: connects to the user's Assistant DO, keeps the chat list in sync, and manages which chat is active.
import { useAssistant } from "@economic/agents-react";
function Chat({ userId, authToken }: { userId: string; authToken: string }) {
const { status, chats, currentChatName, assistant, chat } = useAssistant({
host: "localhost:8787",
agentName: "MyAssistant", // matches the Assistant DO binding/class name
name: userId, // the Assistant is keyed by user
authToken,
toolContext: { locale: "en-DK" }, // forwarded to server tools as the request body
welcomeMessage: "Hi! How can I help?",
});
return (
<div>
<button onClick={() => assistant.createChat()}>New chat</button>
<ul>
{chats.map((c) => (
<li key={c.name}>
<button onClick={() => assistant.openChat(c.name)}>{c.title ?? "Untitled"}</button>
<button onClick={() => assistant.deleteChat(c.name)}>Delete</button>
</li>
))}
</ul>
{chat.chat.messages.map((m) => (
<Message key={m.id} message={m} />
))}
<Composer
onSend={(text) => chat.chat.sendMessage({ role: "user", parts: [{ type: "text", text }] })}
/>
</div>
);
}Returns
| Field | Type | Description |
| ----------------- | ----------------------- | --------------------------------------------------------------------------- |
| status | AgentConnectionStatus | Connection status of the assistant, or of the active chat once one is open. |
| chats | ChatSummary[] | The user's chats, most recently updated first. |
| currentChatName | string \| undefined | Id of the active chat, if any. |
| assistant | AssistantActions | getChats, createChat, openChat, deleteChat. |
| chat | useChat result | The active chat — chat.chat is the message interface (see below). |
assistant actions:
| Action | Signature | Description |
| -------------------- | ----------------------------------- | ------------------------------------------------ |
| createChat() | () => Promise<string> | Creates a chat, makes it active, returns its id. |
| openChat(chatId) | (chatId: string) => void | Switches the active chat. |
| getChats() | () => Promise<ChatSummary[]> | Refreshes and returns the chat list. |
| deleteChat(chatId) | (chatId: string) => Promise<void> | Deletes a chat and clears the active selection. |
Titles and summaries are generated server-side a few seconds after the first message, so refetch with
getChats()shortly after sending the opening message if you want the new title to appear.
Options
All hooks share these connection options (useAssistant omits actorId):
| Option | Type | Description |
| -------------------------------- | -------------------------- | ----------------------------------------------------------------------------- |
| host | string | Worker host, e.g. localhost:8787 or my-agent.workers.dev. |
| agentName | string | The agent's DO binding/class name. |
| name | string | DO name. For useAssistant this is the user id; for useChat, the chat key. |
| actorId | string? | If set, the DO name becomes ${actorId}:${name} (useAgent / useChat). |
| authToken | string? | Bearer token; sent as an Authorization header and WebSocket subprotocol. |
| enabled | boolean? | Gate the connection (defaults to enabled when a name is present). |
| sub | { agent; name }[]? | Sub-agent routing (used internally by useAssistant). |
| toolContext | Record<string, unknown>? | Forwarded to server tools as the request body (useChat / useAssistant). |
| welcomeMessage | string? | Seeds an initial assistant message when the chat is empty. |
| onOpen / onClose / onError | event handlers | WebSocket lifecycle callbacks. |
Types
type AgentConnectionStatus = "connecting" | "connected" | "disconnected" | "unauthorized" | "error";
type AgentConnectionType = "agent" | "chat" | "assistant";
type AgentConnectionState = {
status: AgentConnectionStatus;
type: AgentConnectionType;
subAgentName?: string;
};
type ChatSummary = {
name: string;
title?: string;
summary?: string;
created_at: number;
updated_at: number;
};
type MessageFeedback = {
message_id: string;
rating: number; // 1 = up, -1 = down
comment?: string;
created_at: number;
updated_at: number;
};The package also exports the handler types AssistantActions, GetChatsHandler, CreateChatHandler, OpenChatHandler, DeleteChatHandler, MessageFeedbackHandler, and GetMessageFeedbackHandler.
Authentication
Pass a JWT as authToken. It is sent both as an Authorization: Bearer … header and as a WebSocket subprotocol (["bearer", token]), matching the server-side getJwtAuthConfig. When verification fails, the connection status becomes "unauthorized".
.
