# useCandorChat

Wires a `useChat()` result to Candor: messages for <Thread>/<Message>, props for <Composer>, and the tool calls waiting on a person. Configure useChat however you like (transport, tools, onFinish); this only reads it.

Mentions in the composer are sent as structured references in the request body (`references`), not pasted into the text, so the server gets ids.

const chat = useChat({ transport }) const { messages, composer, approvals, respond } = useCandorChat(chat, { agent }) <Thread footer={<Composer {...composer} />}>{messages.map((m) => <Message key={m.id} message={m} />)}</Thread>

- Registry name: `use-candor-chat` (`@candor/use-candor-chat`)
- Tier: Free (MIT). No licence key needed.
- Docs: https://candor.themethodic.com/docs/use-candor-chat

## Install

```bash
npx candor-ui add use-candor-chat
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/use-candor-chat`.
It brings in: ai-sdk, chat, composer.
npm packages: @ai-sdk/react@^4.0.166, ai@^7.0.162.

## Functions

### useCandorChat

```ts
useCandorChat(chat: UseChatHelpers<UIMessage>, options: Omit<FromAiSdkOptions, "chatStatus">)
```

Wires a `useChat()` result to Candor: messages for <Thread>/<Message>, props for <Composer>, and the tool calls waiting on a person. Configure useChat however you like (transport, tools, onFinish); this only reads it.

Mentions in the composer are sent as structured references in the request body (`references`), not pasted into the text, so the server gets ids.

const chat = useChat({ transport }) const { messages, composer, approvals, respond } = useCandorChat(chat, { agent }) <Thread footer={<Composer {...composer} />}>{messages.map((m) => <Message key={m.id} message={m} />)}</Thread>

## Types

### PendingApproval

```ts
{ id: string; toolCallId: string; tool: string; input: unknown; reason?: string | undefined }
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
