# Model picker

Which model the next message uses, as a small menu for Composer's `tools` slot. It takes the same `ModelOption` list as ModelSettings: models are grouped by vendor with their relative cost, and one whose zero-retention terms haven't been attested stays in the list, says so, and can't be picked. The button shows the model the app says is in use, or "Choose a model" when there is none; nothing is chosen for the person. When they can't change it, `locked` says why.

- Registry name: `model-picker` (`@candor/model-picker`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/model-picker

## Install

```bash
npx candor-ui add model-picker
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/model-picker`.
It brings in: bidi, dropdown-menu, know, locale, locked, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { Composer } from "@/components/candor/composer"
import { ModelPicker } from "@/components/candor/model-picker"
import type { ModelOption } from "@/lib/candor/know"

/** The same list ModelSettings takes: every model the app offers, attested or not. */
const models: ModelOption[] = [
  { id: "claude-haiku", vendor: "Anthropic", name: "Claude Haiku", cost: 1, zdr: "attested", note: "Quick lookups" },
  { id: "claude-sonnet", vendor: "Anthropic", name: "Claude Sonnet", cost: 2, zdr: "attested", note: "Most work" },
  { id: "claude-opus", vendor: "Anthropic", name: "Claude Opus", cost: 4, zdr: "attested", note: "Design and review" },
  { id: "gemini-pro", vendor: "Google", name: "Gemini Pro", cost: 3, zdr: "not-attested" },
]

export default function Example() {
  const [value, setValue] = React.useState("")
  const [model, setModel] = React.useState("claude-sonnet")
  return (
    <div className="flex max-w-2xl flex-col gap-6">
      <Composer value={value} onValueChange={setValue} onSubmit={() => setValue("")} tools={<ModelPicker models={models} value={model} onValueChange={setModel} />} />
      {/* When the person can't change it, the picker stays and says why. */}
      <Composer
        value=""
        onValueChange={() => {}}
        onSubmit={() => {}}
        tools={<ModelPicker models={models} value="claude-sonnet" onValueChange={() => {}} locked="Your workspace admin sets the model for this project." />}
      />
    </div>
  )
}
```

## <ModelPicker>

Props:

- `models: ModelOption[]`: Every model the app offers, from any vendor: the same list ModelSettings takes.
- `value?: string | undefined`: The id of the model in use, from your app. Without one, the button says "Choose a model".
- `onValueChange: (id: string) => void`: Called with the id of the model the person picks. Never called for a model that isn't attested.
- `locked?: string | null | undefined`: Why the person can't change the model, e.g. "Your workspace admin sets the model." The button stays, locked, and says it. `null` or left out: unlocked.
- `className?: string | undefined`

## Strings (`modelPicker`)

The fixed words, English by default. Override any of them under `modelPicker` in `<LocaleProvider strings={{ modelPicker: { … } }}>`; the table is exported as `modelPickerStrings`.

- `label`: Model: {name}
- `choose`: Choose a model
- `notAttested`: Zero-retention terms not attested
- `notAvailable`: Not available until {vendor}’s zero-retention terms are attested.
- `cost`: { one: "cost {count} of 4", other: "cost {count} of 4" }

## Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

### ModelOption

Import: `import type { ModelOption } from "@/lib/candor/know"` (item `know`, https://candor.themethodic.com/docs/know.md)

```ts
{
  id: string;
  vendor: ModelVendor;
  name: string;
  /** One line on what it's good for. */
  note?: string | undefined;
  /** Relative cost, 1 (cheapest) to 4. */
  cost: 1 | 2 | 3 | 4;
  /** The vendor's zero-retention contract has been attested by an operator. */
  zdr: "attested" | "not-attested";
}
```

### ModelVendor

Import: `import type { ModelVendor } from "@/lib/candor/know"` (item `know`, https://candor.themethodic.com/docs/know.md)

The four vendors Candor lists first, in this order. Any other vendor ("Meta", "Mistral", "Local") is listed after them, by the name you give.

```ts
"Anthropic" | "Google" | "OpenAI" | "xAI" | (string & {})
```

## Motion

- **tint** (tint): The button's background changes when the pointer is over it; a locked one doesn't change.

Set with `--cd-motion-model-picker-<moment>-*` in your brand file; the families are in the design reference.

## 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`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
