# Claude Agent SDK

Turns Claude Agent SDK events into Candor messages, waiting approvals and usage. Pure, so it can run on every render; useClaudeAgent calls it for you.

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

## Install

```bash
npx candor-ui add claude-agent-sdk
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/claude-agent-sdk`.
It brings in: chat, safe-markdown.
npm packages: @anthropic-ai/claude-agent-sdk@^0.3.318.

## Functions

### fromClaudeAgentEvents

```ts
fromClaudeAgentEvents(events: AgentEvent[], options: FromClaudeAgentOptions)
```

Turns Claude Agent SDK events into Candor messages, waiting approvals and usage. Pure, so it can run on every render; useClaudeAgent calls it for you.

### claudeQuestions

```ts
claudeQuestions(input: unknown)
```

The questions in an AskUserQuestion call, ready for ClarifyingQuestions; undefined when the input isn't one. Option ids are their labels, which is what the SDK takes back.

### claudePermissionResult

```ts
claudePermissionResult(request: Answerable, answer: ClaudeApprovalAnswer)
```

What to hand back to the SDK for a person's answer: the `PermissionResult` canUseTool resolves with. Pure, so it works wherever the answer lands (your own route, a queue, a workflow signal). Questions become AskUserQuestion's `answers`, keyed by question text, several choices joined with ", " and the person's own words used as they are. Only the suggestions the person picked are kept, and none when the request offers no "always allow".

### claudeSuggestions

```ts
claudeSuggestions(updates: PermissionUpdate[] | undefined)
```

The SDK's suggested permission updates as plain parts PermissionPrompt shows, in the same order, so the indexes a person ticks are the ones `alwaysAllow` sends back.

## Types

### ClaudePermissionMode

How the agent's tool calls are approved, as the SDK names it: "default" (ask for anything not allowed already), "acceptEdits", "plan", "auto" (a classifier decides), "dontAsk" (deny what isn't allowed already) and "bypassPermissions". Kept open: a newer SDK's mode passes through as its name.

```ts
"default" | "acceptEdits" | "plan" | "auto" | "dontAsk" | "bypassPermissions" | (string & {})
```

### ClaudeQuestion

One of the agent's questions (the SDK's AskUserQuestion), shaped for ClarifyingQuestions. Options' ids are their labels.

```ts
{ /** The question's text, which is also how its answer is keyed back to the SDK. */ id: string kind: "choice" prompt: string /** The SDK's short header ("Library", "Approach"). */ tag?: string | undefined options: { id: string; label: string; detail?: string | undefined }[] multiple: boolean /** The SDK always lets the person answer in their own words. */ other: true }
```

### ClaudeQuestionAnswer

A person's answer to one ClaudeQuestion: the chosen labels, their own words, or both. Same shape as ClarifyingAnswer.

```ts
{ questionId: string; choice?: string[] | undefined; text?: string | undefined }
```

### ClaudeApprovalAnswer

How a person answered a permission request.

```ts
{ approved: boolean /** Why it was denied, sent to the agent. */ reason?: string | undefined /** The tool's input as the person edited it before allowing it. The tool runs with exactly this. */ updatedInput?: Record<string, unknown> | undefined /** Answers to the agent's questions, when the request is one (`questions`). */ answers?: ClaudeQuestionAnswer[] | undefined /** Which of the request's `su
```

### CandorAgentEvent

Events of Candor's own, mixed into the SDK's stream.

```ts
| { type: "candor_user"; id: string; text: string; references?: EntityRef[] | undefined } /** A tool call is waiting for a person to allow or deny it. */ | { type: "candor_approval" id: string toolUseId: string tool: string input: unknown /** The SDK's own sentence for the request, e.g. "Claude wants to edit notes.md". */ title?: string | undefined description?: string | undefined /** Why the SDK 
```

### AgentEvent

```ts
SDKMessage | CandorAgentEvent
```

### ClaudeApproval

```ts
Omit<Extract<CandorAgentEvent, { type: "candor_approval" }>, "type">
```

### ClaudeAgentUsage

What the SDK measured for the session so far. Cost is its estimate, not a bill.

```ts
{ costUsd: number; inputTokens: number; outputTokens: number; turns: number }
```

### FromClaudeAgentOptions

```ts
{ /** Who the agent is. Candor always names the agent. */ agent: AgentIdentity /** A request is open now: the last reply is still streaming and its approvals can be answered. */ running?: boolean | undefined /** A plain-words title for a tool call: "Read notes.md". */ toolTitle?: ((tool: string, input: unknown) => string | undefined) | undefined /** * The sources a tool's result supports, for the 
```

### ClaudeRecalledMemory

A memory the SDK recalled for a reply: the file (or URL) it came from, and whose memory it is.

```ts
{ path: string; scope: "personal" | "team" | "organization"; content?: string | undefined }
```

### ClaudeAgentState

```ts
{ messages: Message[] /** Tool calls waiting on a person, oldest first. Empty once the request has ended. */ approvals: ClaudeApproval[] /** Send it back with the next message to continue the same session (the SDK's `resume`). */ sessionId?: string | undefined usage?: ClaudeAgentUsage | undefined /** A passing problem the agent is handling itself, such as a retried API call. */ notice?: string | u
```

### ClaudeSuggestion

A "don't ask again" rule in plain parts, for PermissionPrompt's `suggestions`.

```ts
{ action: "allow" | "deny" | "ask" | "remove" | "mode" | "directories" | "other"; what: string; scope?: 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`.
