Skip to content

React SDK

Terminal window
npm install @getstead/client@0.2.0 @getstead/react@0.2.0

useAgent 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>
);
}

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.

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.