@assistant-ui/mcp-docs-server 0.1.34 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.docs/organized/code-examples/waterfall.md +4 -4
  2. package/.docs/organized/code-examples/with-a2a.md +5 -5
  3. package/.docs/organized/code-examples/with-ag-ui.md +6 -6
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +7 -7
  5. package/.docs/organized/code-examples/with-artifacts.md +7 -7
  6. package/.docs/organized/code-examples/with-assistant-transport.md +5 -5
  7. package/.docs/organized/code-examples/with-browser-extension.md +5 -5
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +8 -8
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
  10. package/.docs/organized/code-examples/with-cloud.md +7 -7
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +7 -7
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +9 -9
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +9 -9
  14. package/.docs/organized/code-examples/with-expo.md +43 -17
  15. package/.docs/organized/code-examples/with-external-store.md +5 -5
  16. package/.docs/organized/code-examples/with-ffmpeg.md +7 -7
  17. package/.docs/organized/code-examples/with-generative-ui.md +32 -308
  18. package/.docs/organized/code-examples/with-google-adk.md +5 -5
  19. package/.docs/organized/code-examples/with-heat-graph.md +4 -4
  20. package/.docs/organized/code-examples/with-image-generation.md +7 -7
  21. package/.docs/organized/code-examples/with-interactables.md +7 -7
  22. package/.docs/organized/code-examples/with-langchain.md +7 -7
  23. package/.docs/organized/code-examples/with-langgraph.md +6 -6
  24. package/.docs/organized/code-examples/with-livekit.md +9 -9
  25. package/.docs/organized/code-examples/with-mcp.md +7 -7
  26. package/.docs/organized/code-examples/with-opencode.md +106 -580
  27. package/.docs/organized/code-examples/with-pi.md +2044 -0
  28. package/.docs/organized/code-examples/with-react-hook-form.md +8 -8
  29. package/.docs/organized/code-examples/with-react-ink-web.md +691 -0
  30. package/.docs/organized/code-examples/with-react-ink.md +28 -16
  31. package/.docs/organized/code-examples/with-react-router.md +5 -5
  32. package/.docs/organized/code-examples/with-resumable-stream.md +8 -8
  33. package/.docs/organized/code-examples/with-store.md +14 -10
  34. package/.docs/organized/code-examples/with-tanstack.md +18 -4
  35. package/.docs/organized/code-examples/with-tap-runtime.md +5 -5
  36. package/.docs/organized/code-examples/with-virtualized-thread.md +656 -0
  37. package/.docs/raw/docs/(docs)/architecture.mdx +13 -12
  38. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +44 -1
  39. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -1
  40. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +2 -23
  41. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +22 -0
  42. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +0 -7
  43. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +20 -21
  44. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  45. package/.docs/raw/docs/guides/index.mdx +3 -0
  46. package/.docs/raw/docs/guides/input-history.mdx +55 -0
  47. package/.docs/raw/docs/guides/virtualization.mdx +63 -0
  48. package/.docs/raw/docs/ink/hooks.mdx +2 -2
  49. package/.docs/raw/docs/react-native/hooks.mdx +1 -1
  50. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +1 -3
  51. package/.docs/raw/docs/runtimes/concepts/stability.mdx +1 -1
  52. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +1 -1
  53. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +108 -4
  54. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +6 -6
  55. package/.docs/raw/docs/tools/tool-ui.mdx +37 -1
  56. package/.docs/raw/docs/ui/dot-matrix.mdx +133 -0
  57. package/.docs/raw/docs/ui/model-selector.mdx +219 -52
  58. package/.docs/raw/docs/ui/number-roll.mdx +154 -0
  59. package/.docs/raw/docs/ui/reasoning.mdx +3 -3
  60. package/.docs/raw/docs/ui/streamdown.mdx +2 -0
  61. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
  62. package/.docs/raw/docs/ui/thread.mdx +52 -0
  63. package/.docs/raw/docs/ui/tool-fallback.mdx +2 -2
  64. package/package.json +3 -3
@@ -577,12 +577,48 @@ const toolkit = defineToolkit({
577
577
 
578
578
  - `undefined`: gate is open, the renderer should ask the user. This is the only state in which `respondToApproval` is legal.
579
579
  - `true`: decision recorded as allow. The server is producing the result (or has produced one, available on `result`).
580
- - `false`: decision recorded as deny. `output-denied` from the runtime sets `isError` and exposes `approval.reason`.
580
+ - `false`: decision recorded as deny. The runtime records an error result (`isError`) and exposes `approval.reason`.
581
581
 
582
582
  `approval.isAutomatic` is `true` when the runtime granted the decision from a server-side policy rather than the user; render a "auto-approved" badge instead of buttons in that case.
583
583
 
584
+ Approval gates require a runtime that implements them: the AI SDK v6 runtime emits them for `needsApproval` tools, and `LocalRuntime` supports gates emitted by your `ChatModelAdapter`; see [LocalRuntime approval gates](/docs/runtimes/custom/local-runtime#approval-gates). For tools where the user supplies the result itself, use `unstable_humanToolNames` with `addResult` instead; see [human-in-the-loop tools](/docs/runtimes/custom/local-runtime#human-in-the-loop-tools).
585
+
584
586
  For the wire-side setup (`needsApproval`, `sendAutomaticallyWhen`), see [AI SDK v6 server-side tool approval](/docs/runtimes/ai-sdk/v6#server-side-tool-approval).
585
587
 
588
+ ### Approval options
589
+
590
+ Beyond the plain allow / deny pair, the host can attach a list of decision options to an approval, for example "allow once", "allow for this session", and "always allow". Each option carries a machine-readable `kind` (`"allow-once"`, `"allow-always"`, `"reject-once"`, `"reject-always"`); scope semantics like session versus global belong to the option's `id` and `label`, which only the host interprets:
591
+
592
+ ```ts
593
+ const approval = {
594
+ id: "a1",
595
+ options: [
596
+ { id: "once", kind: "allow-once" },
597
+ { id: "session", kind: "allow-always", label: "Allow for this session" },
598
+ {
599
+ id: "always",
600
+ kind: "allow-always",
601
+ label: "Always allow",
602
+ grants: ["git *"],
603
+ confirm: true,
604
+ },
605
+ { id: "deny", kind: "reject-once" },
606
+ ],
607
+ };
608
+ ```
609
+
610
+ Renderers respond with the chosen option instead of a boolean; the option's kind resolves the decision:
611
+
612
+ ```tsx
613
+ respondToApproval({ optionId: "session" });
614
+ ```
615
+
616
+ The runtime receives `{ approvalId, approved, optionId, reason? }`, so a host that persists "always allow" decisions can key its store off `optionId`. Persistence is entirely host-owned: assistant-ui never stores a decision and never auto-answers future approvals. `grants` lists the patterns an option would persist (shown to the user before they commit), and `confirm` opts the option into a confirmation step. Options with custom `_`-prefixed kinds are skipped by the default `ToolFallback` bar and must be answered with an explicit `approved` value, optionally alongside the `optionId` so the chosen option is still recorded.
617
+
618
+ Approvals that end without a decision (a cancelled run or an expired request) are recorded by the host as `approval.resolution: "cancelled" | "expired"`, which closes the gate without recording a deny.
619
+
620
+ The default `ToolFallback` component renders supplied options automatically, including the confirmation step.
621
+
586
622
  ## Advanced Features
587
623
 
588
624
  ### Tool Status Handling
@@ -0,0 +1,133 @@
1
+ ---
2
+ title: Dot Matrix
3
+ description: Tiny 5x5 dot-matrix indicator with 20 state-specific blink patterns.
4
+ platforms: ["react"]
5
+ ---
6
+
7
+ import { PreviewCode } from "@/components/docs/preview-code.server";
8
+ import {
9
+ DotMatrixSample,
10
+ DotMatrixLifecycleSample,
11
+ DotMatrixInlineSample,
12
+ DotMatrixSizesSample,
13
+ } from "@/components/docs/samples/dot-matrix";
14
+
15
+ <Callout>
16
+ This is a **standalone component** that does not depend on the assistant-ui runtime. Use it anywhere in your application.
17
+ </Callout>
18
+
19
+ <DotMatrixSample />
20
+
21
+ ## Installation
22
+
23
+ <InstallCommand shadcn={["dot-matrix"]} />
24
+
25
+ This adds a `/components/assistant-ui/dot-matrix.tsx` file to your project, which you can adjust as needed. The component has no dependencies beyond React.
26
+
27
+ ## Usage
28
+
29
+ ```tsx
30
+ import { DotMatrix } from "@/components/assistant-ui/dot-matrix";
31
+
32
+ export function RunIndicator({ isRunning }: { isRunning: boolean }) {
33
+ return <DotMatrix state={isRunning ? "loading" : "success"} />;
34
+ }
35
+ ```
36
+
37
+ Dots inherit the surrounding text color, so the matrix renders dark dots on light backgrounds and light dots on dark backgrounds without configuration. Every state is a combination of a dot pattern, a motion, and a color, and switching states cross-fades each dot into its new pattern.
38
+
39
+ ## States
40
+
41
+ | State | Pattern |
42
+ |-------|---------|
43
+ | `idle` | Dim static grid |
44
+ | `loading` | Randomized twinkle (default) |
45
+ | `thinking` | Diagonal wave |
46
+ | `streaming` | Falling rain, per-column streaks |
47
+ | `searching` | Horizontal sweep |
48
+ | `syncing` | Rotating sweep around the center |
49
+ | `connecting` | Ripple expanding from the center |
50
+ | `waiting` | Ellipsis dots blinking in sequence |
51
+ | `uploading` | Wave rising upward |
52
+ | `downloading` | Wave falling downward |
53
+ | `listening` | Slow equalizer columns |
54
+ | `speaking` | Fast equalizer columns |
55
+ | `recording` | Red center dot breathing |
56
+ | `success` | Green check glyph, static |
57
+ | `error` | Red cross glyph, blinking |
58
+ | `warning` | Amber exclamation glyph, slow blink |
59
+ | `info` | Blue info glyph, static |
60
+ | `paused` | Pause bars glyph, static |
61
+ | `stopped` | Square glyph, static |
62
+ | `offline` | Very dim static grid |
63
+
64
+ The component exports `dotMatrixStates` (the ordered list above) and the `DotMatrixState` union type, so UIs can enumerate or map states without duplicating the list. New states are added by extending the `STATES` record in the component source with a glyph and a per-dot blink function.
65
+
66
+ ## Examples
67
+
68
+ ### State Lifecycle
69
+
70
+ Drive the `state` prop from your run status; the matrix morphs between patterns instead of swapping components.
71
+
72
+ <PreviewCode file="components/docs/samples/dot-matrix" name="DotMatrixLifecycleSample">
73
+ <DotMatrixLifecycleSample />
74
+ </PreviewCode>
75
+
76
+ ### Inline With Text
77
+
78
+ At the default `size-4` the matrix aligns with text like an icon, and the dots adapt to inverted surfaces through `currentColor`.
79
+
80
+ <PreviewCode file="components/docs/samples/dot-matrix" name="DotMatrixInlineSample">
81
+ <DotMatrixInlineSample />
82
+ </PreviewCode>
83
+
84
+ ### Sizes
85
+
86
+ The matrix is an SVG, so any size utility scales it crisply.
87
+
88
+ <DotMatrixSizesSample />
89
+
90
+ ## How It Works
91
+
92
+ The grid is a 5x5 SVG of `currentColor` circles. Blinking is a single CSS keyframe animation whose high/low opacity bounds come from registered per-dot CSS variables; the animation runs in every state (static states collapse the bounds) and the bounds carry a transition, which is what makes state changes cross-fade per dot. The randomized loading rhythm uses deterministic per-dot delays and durations, so server and client render identical markup and no JavaScript runs after render. With `prefers-reduced-motion`, the dots hold their resting opacity instead of blinking.
93
+
94
+ The root is a `role="status"` live region whose text content is the state name (or the `label` prop), so screen readers announce state changes; the SVG itself is `aria-hidden`.
95
+
96
+ ## API Reference
97
+
98
+ ### DotMatrix
99
+
100
+ <ParametersTable
101
+ type="DotMatrixProps"
102
+ parameters={[
103
+ {
104
+ name: "state",
105
+ type: "DotMatrixState",
106
+ default: '"loading"',
107
+ description:
108
+ "One of the 20 built-in states listed above, controlling pattern, motion, and color.",
109
+ },
110
+ {
111
+ name: "label",
112
+ type: "string",
113
+ description:
114
+ "Accessible label announced by screen readers. Defaults to the state name.",
115
+ },
116
+ {
117
+ name: "className",
118
+ type: "string",
119
+ description:
120
+ "Additional CSS classes. Use size utilities to scale and text color utilities to recolor.",
121
+ },
122
+ ]}
123
+ />
124
+
125
+ ### Styling
126
+
127
+ Color follows `currentColor`, so `className="text-blue-500"` recolors the whole matrix; the outcome states (`success`, `error`, `warning`, `info`, `recording`, and the muted static states) set their own color which a `className` can override. Dots are targetable via `[data-slot="dot-matrix"]` and `[data-slot="dot-matrix-dot"]`, and the current state is exposed as `data-state` on the root.
128
+
129
+ ## Related Components
130
+
131
+ - [Number Roll](/docs/ui/number-roll) - Animated rolling number
132
+ - [Badge](/docs/ui/badge) - Small status and metadata labels
133
+ - [Voice](/docs/ui/voice) - Voice activity visualization
@@ -1,12 +1,12 @@
1
1
  ---
2
- title: ModelSelector
3
- description: Model picker with unified overlay positioning and runtime integration.
2
+ title: Model Selector
3
+ description: Composable model picker with reasoning effort levels, search, and runtime integration.
4
4
  platforms: ["react"]
5
5
  ---
6
6
 
7
7
  import { ModelSelectorSample } from "@/components/docs/samples/model-selector";
8
8
 
9
- A select component that lets users switch between AI models. Uses item-aligned positioning so the selected model overlays the trigger for a unified look. Integrates with assistant-ui's `ModelContext` system to automatically propagate the selected model to your backend.
9
+ A picker that lets users switch between AI models and choose a reasoning effort (thinking) level. It is built on Popover + Command, so search, provider grouping, and filtering compose in without being built in. The default export integrates with assistant-ui's `ModelContext` system, so the selection reaches your backend on every request with no extra wiring.
10
10
 
11
11
  <ModelSelectorSample />
12
12
 
@@ -24,9 +24,9 @@ A select component that lets users switch between AI models. Uses item-aligned p
24
24
 
25
25
  ### Use in your application
26
26
 
27
- Place the `ModelSelector` inside your thread component, typically in the composer area:
27
+ Place the `ModelSelector` inside your thread component, typically in the composer area. Each model needs an `id` and a display `name`; everything else is optional:
28
28
 
29
- ```tsx title="/components/assistant-ui/thread.tsx" {1,6-14}
29
+ ```tsx title="/components/assistant-ui/thread.tsx" {1,6-15}
30
30
  import { ModelSelector } from "@/components/assistant-ui/model-selector";
31
31
 
32
32
  const ComposerAction: FC = () => {
@@ -36,9 +36,10 @@ const ComposerAction: FC = () => {
36
36
  models={[
37
37
  { id: "gpt-5.4-nano", name: "GPT-5.4 Nano", description: "Fast and efficient" },
38
38
  { id: "gpt-5.4-mini", name: "GPT-5.4 Mini", description: "Balanced performance" },
39
- { id: "gpt-5.5", name: "GPT-5.5", description: "Most capable" },
39
+ { id: "gpt-5.5", name: "GPT-5.5", description: "Most capable", efforts: true },
40
40
  ]}
41
41
  defaultValue="gpt-5.4-nano"
42
+ defaultEffort="medium"
42
43
  size="sm"
43
44
  />
44
45
  </div>
@@ -49,16 +50,22 @@ const ComposerAction: FC = () => {
49
50
  </Step>
50
51
  <Step>
51
52
 
52
- ### Read the model in your API route
53
+ ### Read the selection in your API route
53
54
 
54
- The selected model's `id` is sent as `config.modelName` in the request body:
55
+ The selected model's `id` arrives as `config.modelName`, and the effort level as `config.reasoningEffort`:
55
56
 
56
- ```tsx title="app/api/chat/route.ts" {2,5}
57
+ ```tsx title="app/api/chat/route.ts" {2,5-11}
57
58
  export async function POST(req: Request) {
58
59
  const { messages, config } = await req.json();
59
60
 
60
61
  const result = streamText({
61
62
  model: openai(config?.modelName ?? "gpt-5.4-nano"),
63
+ providerOptions: {
64
+ openai:
65
+ config?.reasoningEffort !== undefined
66
+ ? { reasoningEffort: config.reasoningEffort }
67
+ : {},
68
+ },
62
69
  messages: await convertToModelMessages(messages),
63
70
  });
64
71
 
@@ -66,77 +73,126 @@ export async function POST(req: Request) {
66
73
  }
67
74
  ```
68
75
 
76
+ `config.reasoningEffort` is only present when the selected model supports the chosen level, so the route only forwards it when it exists. See [How It Works](#how-it-works).
77
+
69
78
  </Step>
70
79
  </Steps>
71
80
 
72
- ## Variants
81
+ ## Reasoning Efforts
73
82
 
74
- Use the `variant` prop to change the trigger's visual style.
83
+ A model that declares `efforts` shows a "Thinking" row at the bottom of the popover. `efforts: true` enables the default Low / Medium / High levels; pass a list of `{ id, name }` objects to define your own:
75
84
 
76
85
  ```tsx
77
- <ModelSelector variant="outline" /> // Border (default)
78
- <ModelSelector variant="ghost" /> // No background
79
- <ModelSelector variant="muted" /> // Solid background
86
+ {
87
+ id: "gpt-5.5",
88
+ name: "GPT-5.5",
89
+ efforts: [
90
+ { id: "minimal", name: "Minimal" },
91
+ { id: "high", name: "High" },
92
+ ],
93
+ }
80
94
  ```
81
95
 
82
- | Variant | Description |
83
- | --------- | ---------------------------------------------- |
84
- | `outline` | Border with transparent background (default) |
85
- | `ghost` | No background, subtle hover |
86
- | `muted` | Solid secondary background |
96
+ Omit `efforts` for models without configurable reasoning. The row is hidden while such a model is selected.
87
97
 
88
- ## Sizes
98
+ ### Sticky Selection
89
99
 
90
- Use the `size` prop to control the trigger dimensions.
100
+ The effort selection survives model switches. Switching to a model that doesn't support the current level omits `reasoningEffort` from the request instead of resetting the user's choice, and the level applies again when the user switches back. The exported `resolveModelEffort` helper applies the same rule if you build your own runtime integration around `ModelSelector.Root`; see [`resolveModelEffort`](#resolvemodeleffort).
91
101
 
92
- ```tsx
93
- <ModelSelector size="sm" /> // Compact (h-8, text-xs)
94
- <ModelSelector size="default" /> // Standard (h-9)
95
- <ModelSelector size="lg" /> // Large (h-10)
96
- ```
97
-
98
- ## Model Options
102
+ ### Custom Effort UI
99
103
 
100
- Each model in the `models` array supports:
104
+ `ModelSelector.Effort` lays the levels out as horizontal segments, which overflows the popover width once a model has more than a few. For those cases, or for a different layout such as a slider or a sub-dropdown, build your own control with the `useModelSelectorEfforts` hook. It exposes the selected model's levels and the active selection:
101
105
 
102
106
  ```tsx
103
- const models = [
104
- {
105
- id: "gpt-5.4-nano", // Sent to backend as config.modelName
106
- name: "GPT-5.4 Nano", // Display name in trigger and items
107
- description: "Fast and efficient", // Optional subtitle in items only
108
- icon: <SparklesIcon />, // Optional icon (any ReactNode)
109
- },
110
- ];
107
+ import { useModelSelectorEfforts } from "@/components/assistant-ui/model-selector";
108
+ import {
109
+ DropdownMenu,
110
+ DropdownMenuContent,
111
+ DropdownMenuRadioGroup,
112
+ DropdownMenuRadioItem,
113
+ DropdownMenuTrigger,
114
+ } from "@/components/ui/dropdown-menu";
115
+
116
+ function EffortDropdown() {
117
+ const { efforts, effort, setEffort } = useModelSelectorEfforts();
118
+ if (!efforts?.length) return null;
119
+
120
+ return (
121
+ <div className="flex items-center justify-between gap-3 border-t px-3 py-2">
122
+ <span className="text-muted-foreground text-xs">Thinking</span>
123
+ <DropdownMenu>
124
+ <DropdownMenuTrigger className="text-xs">
125
+ {efforts.find((e) => e.id === effort)?.name ?? "Select"}
126
+ </DropdownMenuTrigger>
127
+ <DropdownMenuContent align="end">
128
+ <DropdownMenuRadioGroup value={effort} onValueChange={setEffort}>
129
+ {efforts.map((option) => (
130
+ <DropdownMenuRadioItem key={option.id} value={option.id}>
131
+ {option.name}
132
+ </DropdownMenuRadioItem>
133
+ ))}
134
+ </DropdownMenuRadioGroup>
135
+ </DropdownMenuContent>
136
+ </DropdownMenu>
137
+ </div>
138
+ );
139
+ }
111
140
  ```
112
141
 
113
- ## Runtime Integration
142
+ Render it inside `ModelSelector.Content` in place of `ModelSelector.Effort`. The same hook supports any shape that reads the levels and writes the selection.
114
143
 
115
- The default `ModelSelector` export automatically registers the selected model with assistant-ui's `ModelContext` system. When a user selects a model:
144
+ ## Search
116
145
 
117
- 1. The component calls `aui.modelContext().register()` with `config.modelName`
118
- 2. The `AssistantChatTransport` includes `config` in the request body
119
- 3. Your API route reads `config.modelName` to determine which model to use
146
+ Search is opt-in. Pass `searchable` to the default component:
120
147
 
121
- This works out of the box with `@assistant-ui/react-ai-sdk`.
148
+ ```tsx
149
+ <ModelSelector models={models} searchable />
150
+ ```
122
151
 
123
- ## API Reference
152
+ Or compose `ModelSelector.Search` into a custom layout. Matching runs against each model's `id`, `name`, and `keywords`; add the provider name to `keywords` so typing "openai" finds its models.
124
153
 
125
- ### Composable API
154
+ ## Composition
126
155
 
127
- For custom layouts, use the sub-components directly with `ModelSelector.Root`:
156
+ All parts are exported individually. The default popover content is `List` + `Effort`; replace it to add search, provider groups, or anything else:
128
157
 
129
158
  ```tsx
130
159
  import {
131
160
  ModelSelectorRoot,
132
161
  ModelSelectorTrigger,
133
162
  ModelSelectorContent,
163
+ ModelSelectorSearch,
164
+ ModelSelectorList,
165
+ ModelSelectorEmpty,
166
+ ModelSelectorGroup,
134
167
  ModelSelectorItem,
168
+ ModelSelectorEffort,
135
169
  } from "@/components/assistant-ui/model-selector";
136
170
 
137
- <ModelSelectorRoot models={models} value={modelId} onValueChange={setModelId}>
171
+ <ModelSelectorRoot
172
+ models={models}
173
+ value={modelId}
174
+ onValueChange={setModelId}
175
+ effort={effort}
176
+ onEffortChange={setEffort}
177
+ >
138
178
  <ModelSelectorTrigger variant="outline" />
139
- <ModelSelectorContent />
179
+ <ModelSelectorContent>
180
+ <ModelSelectorSearch placeholder="Search models..." />
181
+ <ModelSelectorList>
182
+ <ModelSelectorEmpty />
183
+ <ModelSelectorGroup heading="OpenAI">
184
+ {openaiModels.map((model) => (
185
+ <ModelSelectorItem key={model.id} model={model} />
186
+ ))}
187
+ </ModelSelectorGroup>
188
+ <ModelSelectorGroup heading="Anthropic">
189
+ {anthropicModels.map((model) => (
190
+ <ModelSelectorItem key={model.id} model={model} />
191
+ ))}
192
+ </ModelSelectorGroup>
193
+ </ModelSelectorList>
194
+ <ModelSelectorEffort label="Thinking" />
195
+ </ModelSelectorContent>
140
196
  </ModelSelectorRoot>
141
197
  ```
142
198
 
@@ -144,9 +200,64 @@ import {
144
200
  |-----------|-------------|
145
201
  | `ModelSelector` | Default export with runtime integration |
146
202
  | `ModelSelector.Root` | Presentational root (no runtime, controlled state) |
147
- | `ModelSelector.Trigger` | CVA-styled trigger showing current model |
148
- | `ModelSelector.Content` | Select content with model items |
149
- | `ModelSelector.Item` | Individual model option with icon, name, description |
203
+ | `ModelSelector.Trigger` | CVA-styled trigger showing the current selection |
204
+ | `ModelSelector.Value` | Selected model name, icon, and active effort |
205
+ | `ModelSelector.Content` | Popover content wrapping a Command |
206
+ | `ModelSelector.Search` | Search input that filters the list |
207
+ | `ModelSelector.List` | List of model items (renders all models by default) |
208
+ | `ModelSelector.Empty` | Empty state shown when search has no matches |
209
+ | `ModelSelector.Group` | Labeled group of items (e.g. by provider) |
210
+ | `ModelSelector.Separator` | Divider between groups or items |
211
+ | `ModelSelector.Item` | Individual model option |
212
+ | `ModelSelector.Effort` | Thinking level row for the selected model |
213
+
214
+ `ModelSelector.List` is a Command list, so filtering and keyboard navigation work across groups automatically. Custom sorting is plain code: order the models before rendering items.
215
+
216
+ <Callout type="warn">
217
+ `ModelSelector.Content` wraps a Command whose root keydown handler claims
218
+ <kbd>Enter</kbd> to select the highlighted model. Interactive elements
219
+ composed inside it (filter chips, custom effort controls) should stop
220
+ propagation for Enter in their own `onKeyDown` so the focused control
221
+ activates instead. `ModelSelector.Effort` already does this.
222
+ </Callout>
223
+
224
+ ## Variants
225
+
226
+ Use the `variant` prop to change the trigger's visual style.
227
+
228
+ ```tsx
229
+ <ModelSelector variant="outline" /> // Border (default)
230
+ <ModelSelector variant="ghost" /> // No background
231
+ <ModelSelector variant="muted" /> // Solid background
232
+ ```
233
+
234
+ | Variant | Description |
235
+ | --------- | ---------------------------------------------- |
236
+ | `outline` | Border with transparent background (default) |
237
+ | `ghost` | No background, subtle hover |
238
+ | `muted` | Solid secondary background |
239
+
240
+ ## Sizes
241
+
242
+ Use the `size` prop to control the trigger dimensions.
243
+
244
+ ```tsx
245
+ <ModelSelector size="sm" /> // Compact (h-8, text-xs)
246
+ <ModelSelector size="default" /> // Standard (h-9)
247
+ <ModelSelector size="lg" /> // Large (h-10)
248
+ ```
249
+
250
+ ## How It Works
251
+
252
+ The default `ModelSelector` export registers the selection with assistant-ui's `ModelContext` system:
253
+
254
+ 1. The component calls `aui.modelContext().register()` with `config.modelName`, plus `config.reasoningEffort` when the selected model supports the chosen level
255
+ 2. The `AssistantChatTransport` includes `config` in the request body of every chat request
256
+ 3. Your API route reads `config.modelName` and `config.reasoningEffort`
257
+
258
+ This works out of the box with `@assistant-ui/react-ai-sdk`. `ModelSelector.Root` performs no registration; it is purely presentational, with controlled and uncontrolled props for the value, effort, and open state.
259
+
260
+ ## API Reference
150
261
 
151
262
  ### ModelSelector
152
263
 
@@ -162,7 +273,7 @@ import {
162
273
  {
163
274
  name: "defaultValue",
164
275
  type: "string",
165
- description: "Initial model ID for uncontrolled usage.",
276
+ description: "Initial model ID for uncontrolled usage. Defaults to the first model, captured on first render; if models loads asynchronously, control the value instead.",
166
277
  },
167
278
  {
168
279
  name: "value",
@@ -174,6 +285,27 @@ import {
174
285
  type: "(value: string) => void",
175
286
  description: "Callback when selected model changes.",
176
287
  },
288
+ {
289
+ name: "defaultEffort",
290
+ type: "string",
291
+ description: "Initial effort level ID for uncontrolled usage.",
292
+ },
293
+ {
294
+ name: "effort",
295
+ type: "string",
296
+ description: "Controlled effort level ID.",
297
+ },
298
+ {
299
+ name: "onEffortChange",
300
+ type: "(effort: string) => void",
301
+ description: "Callback when effort level changes.",
302
+ },
303
+ {
304
+ name: "searchable",
305
+ type: "boolean",
306
+ default: "false",
307
+ description: "Render a search input above the model list.",
308
+ },
177
309
  {
178
310
  name: "variant",
179
311
  type: '"outline" | "ghost" | "muted"',
@@ -221,5 +353,40 @@ import {
221
353
  type: "React.ReactNode",
222
354
  description: "Optional icon displayed before the model name.",
223
355
  },
356
+ {
357
+ name: "disabled",
358
+ type: "boolean",
359
+ description: "Disable selection of this model.",
360
+ },
361
+ {
362
+ name: "keywords",
363
+ type: "string[]",
364
+ description: "Extra search terms matched by ModelSelector.Search (e.g. the provider name).",
365
+ },
366
+ {
367
+ name: "efforts",
368
+ type: "boolean | ModelSelectorEffortOption[]",
369
+ description: "Reasoning effort levels. true enables the default Low/Medium/High; pass a custom { id, name } list to override. Omit for models without configurable reasoning.",
370
+ },
224
371
  ]}
225
372
  />
373
+
374
+ ### `useModelSelectorEfforts`
375
+
376
+ ```tsx
377
+ const { efforts, effort, setEffort } = useModelSelectorEfforts();
378
+ ```
379
+
380
+ The selected model's effort levels and the active selection, for building a [custom effort UI](#custom-effort-ui) inside `ModelSelector.Content`. `efforts` is `undefined` for models without configurable reasoning.
381
+
382
+ ### `resolveModelEffort`
383
+
384
+ ```tsx
385
+ resolveModelEffort(models, modelId, effort); // => string | undefined
386
+ ```
387
+
388
+ Returns the effort ID when the given model supports it, otherwise `undefined`. This is the [sticky selection](#sticky-selection) rule the default component applies before registering the selection.
389
+
390
+ ## Related
391
+
392
+ - [Model Context](/docs/copilots/model-context): How registered context (instructions, tools, config) reaches your backend