React SDK
npm install @getstead/client@0.2.0 @getstead/react@0.2.0useAgent binds a customer client to React state. Create a stable SteadClient for the signed-in customer and remount the chat when that identity changes. Use the starter for the complete authentication wrapper.
import { useState, type FormEvent } from 'react';import type { SteadClient } from '@getstead/client';import { useAgent } from '@getstead/react';
export function SupportChat({ client }: { client: SteadClient }) { const [input, setInput] = useState(''); const chat = useAgent(client, { agent: 'support-agent', conversation: { key: 'support-main' }, });
async function submit(event: FormEvent) { event.preventDefault(); if (!input.trim() || chat.busy) return; const text = input; setInput(''); await chat.send(text); }
return ( <section aria-label="Support chat"> <div aria-live="polite"> {chat.messages.map((message, index) => ( <p key={index}><strong>{message.role}:</strong> {message.text}</p> ))} </div> {chat.approval && <p>Waiting for the project owner's approval.</p>} {chat.conflict && <p>Another turn is still running. Try again when it ends.</p>} {chat.error != null && <p role="alert">{String(chat.error)}</p>} <form onSubmit={submit}> <label>Message <input value={input} onChange={e => setInput(e.target.value)} /></label> <button disabled={chat.busy || !input.trim()}>Send</button> </form> {chat.busy && ( <button disabled={chat.cancelling} onClick={() => void chat.stop()}> {chat.cancelling ? 'Stopping…' : 'Stop'} </button> )} </section> );}Options
Section titled “Options”useAgent(client, options) accepts an AgentClient and:
| Option | Meaning |
|---|---|
agent |
Required deployed agent name with endUserRuns enabled. |
conversationId |
Optional existing customer-owned conversation. Takes precedence over conversation. |
conversation: { key? } |
Create a conversation; reuse the key after remounts. Requires the deployed agent’s conversations opt-in. |
Without conversation options, each send is an independent run. A later send detaches the previous stream, not the previous server run. With a conversation, the hook prevents a local overlapping turn and reports a server-side concurrent-turn refusal as conflict.
Returned state and actions
Section titled “Returned state and actions”| Field | Meaning |
|---|---|
messages |
`{ role: ‘user’ |
status |
idle, then the run status. |
connection |
idle, connecting, open, reconnecting, or closed. |
approval |
Pending { id, stepIndex, name, tool, reason }, with optional details represented as undefined. The customer cannot resolve it. |
error |
Failed start, stream refusal/retry exhaustion, or cancellation-request error. |
busy |
True while the watched turn is in progress. |
cancelling |
A server cancellation request is settling. |
conflict |
Another unfinished turn occupies this conversation. Cleared on the next send. |
send(input) |
Starts and attaches a stream. Its promise does not wait for run completion. |
stop() |
Requests durable cancellation and continues watching. Errors are reflected in state; the promise does not throw. |
Unmounting closes local streams and refuses later sends through stale callbacks. It does not automatically cancel a run. Conversation hydration is a best-effort read of recent history; use the client’s history methods for pagination and explicit retry UI.
The package exports useAgent and the types AgentClient, AgentMessage, PendingApproval, UseAgentOptions, UseAgentResult, and UseAgentState. AgentClient is the subset of client methods needed by the hook: start/cancel/stream a run and create/read a conversation.