@assistant-ui/mcp-docs-server 0.1.24 → 0.1.26

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 (151) hide show
  1. package/.docs/organized/code-examples/waterfall.md +8 -6
  2. package/.docs/organized/code-examples/with-a2a.md +676 -0
  3. package/.docs/organized/code-examples/with-ag-ui.md +10 -11
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +31 -19
  5. package/.docs/organized/code-examples/with-artifacts.md +8 -8
  6. package/.docs/organized/code-examples/with-assistant-transport.md +6 -6
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +37 -29
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +14 -11
  9. package/.docs/organized/code-examples/with-cloud.md +8 -8
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +10 -10
  11. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +11 -11
  12. package/.docs/organized/code-examples/with-expo.md +571 -520
  13. package/.docs/organized/code-examples/with-external-store.md +6 -6
  14. package/.docs/organized/code-examples/with-ffmpeg.md +8 -8
  15. package/.docs/organized/code-examples/with-google-adk.md +353 -0
  16. package/.docs/organized/code-examples/with-heat-graph.md +304 -0
  17. package/.docs/organized/code-examples/with-interactables.md +778 -0
  18. package/.docs/organized/code-examples/with-langgraph.md +28 -26
  19. package/.docs/organized/code-examples/with-parent-id-grouping.md +7 -7
  20. package/.docs/organized/code-examples/with-react-hook-form.md +9 -9
  21. package/.docs/organized/code-examples/with-react-ink.md +265 -0
  22. package/.docs/organized/code-examples/with-react-router.md +12 -12
  23. package/.docs/organized/code-examples/with-store.md +33 -22
  24. package/.docs/organized/code-examples/with-tanstack.md +10 -10
  25. package/.docs/organized/code-examples/with-tap-runtime.md +12 -10
  26. package/.docs/raw/blog/2025-01-31-changelog/index.mdx +1 -1
  27. package/.docs/raw/blog/2026-03-launch-week/index.mdx +258 -0
  28. package/.docs/raw/docs/(docs)/architecture.mdx +1 -1
  29. package/.docs/raw/docs/(docs)/cli.mdx +74 -9
  30. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +8 -3
  31. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +5 -1
  32. package/.docs/raw/docs/(docs)/copilots/{make-assistant-readable.mdx → make-assistant-visible.mdx} +14 -5
  33. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +11 -11
  34. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +2 -2
  35. package/.docs/raw/docs/(docs)/devtools.mdx +3 -2
  36. package/.docs/raw/docs/(docs)/guides/attachments.mdx +74 -15
  37. package/.docs/raw/docs/(docs)/guides/branching.mdx +11 -6
  38. package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +18 -16
  39. package/.docs/raw/docs/(docs)/guides/context-api.mdx +81 -43
  40. package/.docs/raw/docs/(docs)/guides/dictation.mdx +5 -5
  41. package/.docs/raw/docs/(docs)/guides/editing.mdx +16 -7
  42. package/.docs/raw/docs/(docs)/guides/interactables.mdx +292 -0
  43. package/.docs/raw/docs/(docs)/guides/latex.mdx +3 -0
  44. package/.docs/raw/docs/(docs)/guides/message-timing.mdx +5 -4
  45. package/.docs/raw/docs/(docs)/guides/multi-agent.mdx +174 -0
  46. package/.docs/raw/docs/(docs)/guides/quoting.mdx +55 -206
  47. package/.docs/raw/docs/(docs)/guides/speech.mdx +1 -4
  48. package/.docs/raw/docs/(docs)/guides/suggestions.mdx +9 -15
  49. package/.docs/raw/docs/(docs)/guides/tool-ui.mdx +17 -7
  50. package/.docs/raw/docs/(docs)/guides/tools.mdx +24 -9
  51. package/.docs/raw/docs/(docs)/index.mdx +3 -3
  52. package/.docs/raw/docs/(docs)/installation.mdx +69 -46
  53. package/.docs/raw/docs/(reference)/api-reference/context-providers/text-message-part-provider.mdx +20 -6
  54. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +24 -4
  55. package/.docs/raw/docs/(reference)/api-reference/integrations/react-hook-form.mdx +1 -1
  56. package/.docs/raw/docs/(reference)/api-reference/integrations/vercel-ai-sdk.mdx +20 -19
  57. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +28 -53
  58. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +4 -4
  59. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-modal.mdx +7 -1
  60. package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +20 -14
  61. package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +1 -1
  62. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +226 -44
  63. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +52 -40
  64. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +343 -23
  65. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +4 -6
  66. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item.mdx +4 -2
  67. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +3 -5
  68. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +169 -22
  69. package/.docs/raw/docs/(reference)/api-reference/runtimes/assistant-runtime.mdx +14 -4
  70. package/.docs/raw/docs/(reference)/api-reference/runtimes/attachment-runtime.mdx +15 -26
  71. package/.docs/raw/docs/(reference)/api-reference/runtimes/composer-runtime.mdx +39 -21
  72. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-part-runtime.mdx +33 -9
  73. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-runtime.mdx +48 -21
  74. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-item-runtime.mdx +36 -7
  75. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +30 -10
  76. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-runtime.mdx +12 -10
  77. package/.docs/raw/docs/(reference)/migrations/deprecation-policy.mdx +1 -1
  78. package/.docs/raw/docs/(reference)/migrations/react-langgraph-v0-7.mdx +9 -4
  79. package/.docs/raw/docs/(reference)/migrations/v0-11.mdx +7 -5
  80. package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +9 -7
  81. package/.docs/raw/docs/(reference)/migrations/v0-14.mdx +159 -0
  82. package/.docs/raw/docs/(reference)/react-compatibility.mdx +5 -134
  83. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +90 -6
  84. package/.docs/raw/docs/cloud/ai-sdk.mdx +95 -5
  85. package/.docs/raw/docs/cloud/langgraph.mdx +13 -3
  86. package/.docs/raw/docs/ink/adapters.mdx +41 -0
  87. package/.docs/raw/docs/ink/custom-backend.mdx +203 -0
  88. package/.docs/raw/docs/ink/hooks.mdx +448 -0
  89. package/.docs/raw/docs/ink/index.mdx +239 -0
  90. package/.docs/raw/docs/ink/migration.mdx +140 -0
  91. package/.docs/raw/docs/ink/primitives.mdx +840 -0
  92. package/.docs/raw/docs/primitives/action-bar.mdx +351 -0
  93. package/.docs/raw/docs/primitives/assistant-modal.mdx +215 -0
  94. package/.docs/raw/docs/primitives/attachment.mdx +216 -0
  95. package/.docs/raw/docs/primitives/branch-picker.mdx +221 -0
  96. package/.docs/raw/docs/primitives/chain-of-thought.mdx +311 -0
  97. package/.docs/raw/docs/primitives/composer.mdx +526 -0
  98. package/.docs/raw/docs/primitives/error.mdx +141 -0
  99. package/.docs/raw/docs/primitives/index.mdx +98 -0
  100. package/.docs/raw/docs/primitives/message.mdx +524 -0
  101. package/.docs/raw/docs/primitives/selection-toolbar.mdx +165 -0
  102. package/.docs/raw/docs/primitives/suggestion.mdx +242 -0
  103. package/.docs/raw/docs/primitives/thread-list.mdx +404 -0
  104. package/.docs/raw/docs/primitives/thread.mdx +482 -0
  105. package/.docs/raw/docs/react-native/adapters.mdx +63 -87
  106. package/.docs/raw/docs/react-native/custom-backend.mdx +11 -14
  107. package/.docs/raw/docs/react-native/hooks.mdx +214 -232
  108. package/.docs/raw/docs/react-native/index.mdx +118 -159
  109. package/.docs/raw/docs/react-native/migration.mdx +144 -0
  110. package/.docs/raw/docs/react-native/primitives.mdx +431 -302
  111. package/.docs/raw/docs/runtimes/a2a/index.mdx +294 -0
  112. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +9 -9
  113. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +14 -3
  114. package/.docs/raw/docs/runtimes/assistant-transport.mdx +59 -25
  115. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +13 -6
  116. package/.docs/raw/docs/runtimes/custom/external-store.mdx +138 -38
  117. package/.docs/raw/docs/runtimes/custom/local.mdx +184 -42
  118. package/.docs/raw/docs/runtimes/data-stream.mdx +92 -19
  119. package/.docs/raw/docs/runtimes/google-adk/index.mdx +624 -0
  120. package/.docs/raw/docs/runtimes/helicone.mdx +6 -6
  121. package/.docs/raw/docs/runtimes/langgraph/index.mdx +38 -27
  122. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +1 -1
  123. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +15 -20
  124. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -11
  125. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +8 -11
  126. package/.docs/raw/docs/runtimes/langserve.mdx +6 -7
  127. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +18 -3
  128. package/.docs/raw/docs/ui/file.mdx +5 -4
  129. package/.docs/raw/docs/ui/image.mdx +5 -4
  130. package/.docs/raw/docs/ui/markdown.mdx +3 -1
  131. package/.docs/raw/docs/ui/mention.mdx +168 -0
  132. package/.docs/raw/docs/ui/model-selector.mdx +8 -8
  133. package/.docs/raw/docs/ui/part-grouping.mdx +7 -10
  134. package/.docs/raw/docs/ui/quote.mdx +210 -0
  135. package/.docs/raw/docs/ui/reasoning.mdx +12 -11
  136. package/.docs/raw/docs/ui/sources.mdx +88 -17
  137. package/.docs/raw/docs/ui/streamdown.mdx +16 -7
  138. package/.docs/raw/docs/ui/thread-list.mdx +11 -13
  139. package/.docs/raw/docs/ui/thread.mdx +28 -33
  140. package/.docs/raw/docs/ui/tool-fallback.mdx +5 -6
  141. package/.docs/raw/docs/ui/tool-group.mdx +9 -8
  142. package/.docs/raw/docs/utilities/heat-graph.mdx +236 -0
  143. package/.docs/raw/docs/utilities/tw-shimmer.mdx +211 -0
  144. package/package.json +5 -5
  145. package/.docs/raw/docs/(reference)/legacy/styled/assistant-modal.mdx +0 -77
  146. package/.docs/raw/docs/(reference)/legacy/styled/decomposition.mdx +0 -635
  147. package/.docs/raw/docs/(reference)/legacy/styled/markdown.mdx +0 -77
  148. package/.docs/raw/docs/(reference)/legacy/styled/scrollbar.mdx +0 -72
  149. package/.docs/raw/docs/(reference)/legacy/styled/thread-width.mdx +0 -22
  150. package/.docs/raw/docs/(reference)/legacy/styled/thread.mdx +0 -77
  151. /package/.docs/raw/docs/cloud/{overview.mdx → index.mdx} +0 -0
@@ -0,0 +1,778 @@
1
+ # Example: with-interactables
2
+
3
+ ## app/api/chat/route.ts
4
+
5
+ ```typescript
6
+ import { openai } from "@ai-sdk/openai";
7
+ import {
8
+ streamText,
9
+ convertToModelMessages,
10
+ stepCountIs,
11
+ jsonSchema,
12
+ } from "ai";
13
+ import type { UIMessage } from "ai";
14
+
15
+ export const maxDuration = 30;
16
+
17
+ type ToolDef = { description?: string; parameters: Record<string, unknown> };
18
+
19
+ export async function POST(req: Request) {
20
+ const {
21
+ messages,
22
+ system,
23
+ tools: clientTools,
24
+ }: {
25
+ messages: UIMessage[];
26
+ system?: string;
27
+ tools?: Record<string, ToolDef>;
28
+ } = await req.json();
29
+
30
+ // Convert client-defined tools (forwarded from model context) to AI SDK format.
31
+ // These have no `execute` — they are frontend tools executed on the client
32
+ // via useAssistantTool / useInteractable.
33
+ const tools = clientTools
34
+ ? Object.fromEntries(
35
+ Object.entries(clientTools).map(([name, def]) => [
36
+ name,
37
+ {
38
+ description: def.description ?? "",
39
+ inputSchema: jsonSchema(def.parameters),
40
+ },
41
+ ]),
42
+ )
43
+ : undefined;
44
+
45
+ const result = streamText({
46
+ model: openai("gpt-4o"),
47
+ messages: await convertToModelMessages(messages),
48
+ stopWhen: stepCountIs(10),
49
+ ...(system ? { system } : {}),
50
+ ...(tools ? { tools } : {}),
51
+ } as Parameters<typeof streamText>[0]);
52
+
53
+ return result.toUIMessageStreamResponse();
54
+ }
55
+
56
+ ```
57
+
58
+ ## app/globals.css
59
+
60
+ ```css
61
+ @import "tailwindcss";
62
+ @import "tw-animate-css";
63
+
64
+ @source "../../../packages/ui/src";
65
+
66
+ @custom-variant dark (&:is(.dark *));
67
+
68
+ @theme inline {
69
+ --radius-sm: calc(var(--radius) - 4px);
70
+ --radius-md: calc(var(--radius) - 2px);
71
+ --radius-lg: var(--radius);
72
+ --radius-xl: calc(var(--radius) + 4px);
73
+ --color-background: var(--background);
74
+ --color-foreground: var(--foreground);
75
+ --color-card: var(--card);
76
+ --color-card-foreground: var(--card-foreground);
77
+ --color-popover: var(--popover);
78
+ --color-popover-foreground: var(--popover-foreground);
79
+ --color-primary: var(--primary);
80
+ --color-primary-foreground: var(--primary-foreground);
81
+ --color-secondary: var(--secondary);
82
+ --color-secondary-foreground: var(--secondary-foreground);
83
+ --color-muted: var(--muted);
84
+ --color-muted-foreground: var(--muted-foreground);
85
+ --color-accent: var(--accent);
86
+ --color-accent-foreground: var(--accent-foreground);
87
+ --color-destructive: var(--destructive);
88
+ --color-border: var(--border);
89
+ --color-input: var(--input);
90
+ --color-ring: var(--ring);
91
+ --color-chart-1: var(--chart-1);
92
+ --color-chart-2: var(--chart-2);
93
+ --color-chart-3: var(--chart-3);
94
+ --color-chart-4: var(--chart-4);
95
+ --color-chart-5: var(--chart-5);
96
+ --color-sidebar: var(--sidebar);
97
+ --color-sidebar-foreground: var(--sidebar-foreground);
98
+ --color-sidebar-primary: var(--sidebar-primary);
99
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
100
+ --color-sidebar-accent: var(--sidebar-accent);
101
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
102
+ --color-sidebar-border: var(--sidebar-border);
103
+ --color-sidebar-ring: var(--sidebar-ring);
104
+ }
105
+
106
+ :root {
107
+ --radius: 0.625rem;
108
+ --background: oklch(1 0 0);
109
+ --foreground: oklch(0.141 0.005 285.823);
110
+ --card: oklch(1 0 0);
111
+ --card-foreground: oklch(0.141 0.005 285.823);
112
+ --popover: oklch(1 0 0);
113
+ --popover-foreground: oklch(0.141 0.005 285.823);
114
+ --primary: oklch(0.21 0.006 285.885);
115
+ --primary-foreground: oklch(0.985 0 0);
116
+ --secondary: oklch(0.967 0.001 286.375);
117
+ --secondary-foreground: oklch(0.21 0.006 285.885);
118
+ --muted: oklch(0.967 0.001 286.375);
119
+ --muted-foreground: oklch(0.552 0.016 285.938);
120
+ --accent: oklch(0.967 0.001 286.375);
121
+ --accent-foreground: oklch(0.21 0.006 285.885);
122
+ --destructive: oklch(0.577 0.245 27.325);
123
+ --border: oklch(0.92 0.004 286.32);
124
+ --input: oklch(0.92 0.004 286.32);
125
+ --ring: oklch(0.705 0.015 286.067);
126
+ --chart-1: oklch(0.646 0.222 41.116);
127
+ --chart-2: oklch(0.6 0.118 184.704);
128
+ --chart-3: oklch(0.398 0.07 227.392);
129
+ --chart-4: oklch(0.828 0.189 84.429);
130
+ --chart-5: oklch(0.769 0.188 70.08);
131
+ --sidebar: oklch(0.985 0 0);
132
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
133
+ --sidebar-primary: oklch(0.21 0.006 285.885);
134
+ --sidebar-primary-foreground: oklch(0.985 0 0);
135
+ --sidebar-accent: oklch(0.967 0.001 286.375);
136
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
137
+ --sidebar-border: oklch(0.92 0.004 286.32);
138
+ --sidebar-ring: oklch(0.705 0.015 286.067);
139
+ }
140
+
141
+ .dark {
142
+ --background: oklch(0.141 0.005 285.823);
143
+ --foreground: oklch(0.985 0 0);
144
+ --card: oklch(0.21 0.006 285.885);
145
+ --card-foreground: oklch(0.985 0 0);
146
+ --popover: oklch(0.21 0.006 285.885);
147
+ --popover-foreground: oklch(0.985 0 0);
148
+ --primary: oklch(0.92 0.004 286.32);
149
+ --primary-foreground: oklch(0.21 0.006 285.885);
150
+ --secondary: oklch(0.274 0.006 286.033);
151
+ --secondary-foreground: oklch(0.985 0 0);
152
+ --muted: oklch(0.274 0.006 286.033);
153
+ --muted-foreground: oklch(0.705 0.015 286.067);
154
+ --accent: oklch(0.274 0.006 286.033);
155
+ --accent-foreground: oklch(0.985 0 0);
156
+ --destructive: oklch(0.704 0.191 22.216);
157
+ --border: oklch(1 0 0 / 10%);
158
+ --input: oklch(1 0 0 / 15%);
159
+ --ring: oklch(0.552 0.016 285.938);
160
+ --chart-1: oklch(0.488 0.243 264.376);
161
+ --chart-2: oklch(0.696 0.17 162.48);
162
+ --chart-3: oklch(0.769 0.188 70.08);
163
+ --chart-4: oklch(0.627 0.265 303.9);
164
+ --chart-5: oklch(0.645 0.246 16.439);
165
+ --sidebar: oklch(0.21 0.006 285.885);
166
+ --sidebar-foreground: oklch(0.985 0 0);
167
+ --sidebar-primary: oklch(0.488 0.243 264.376);
168
+ --sidebar-primary-foreground: oklch(0.985 0 0);
169
+ --sidebar-accent: oklch(0.274 0.006 286.033);
170
+ --sidebar-accent-foreground: oklch(0.985 0 0);
171
+ --sidebar-border: oklch(1 0 0 / 10%);
172
+ --sidebar-ring: oklch(0.552 0.016 285.938);
173
+ }
174
+
175
+ @layer base {
176
+ * {
177
+ @apply border-border outline-ring/50;
178
+ }
179
+ body {
180
+ @apply bg-background text-foreground;
181
+ }
182
+ }
183
+
184
+ ```
185
+
186
+ ## app/layout.tsx
187
+
188
+ ```tsx
189
+ import type { Metadata } from "next";
190
+ import "./globals.css";
191
+
192
+ export const metadata: Metadata = {
193
+ title: "assistant-ui Interactables Example",
194
+ description:
195
+ "Example using assistant-ui interactable components with a task board",
196
+ };
197
+
198
+ export default function RootLayout({
199
+ children,
200
+ }: Readonly<{
201
+ children: React.ReactNode;
202
+ }>) {
203
+ return (
204
+ <html lang="en">
205
+ <body className="h-dvh">{children}</body>
206
+ </html>
207
+ );
208
+ }
209
+
210
+ ```
211
+
212
+ ## app/page.tsx
213
+
214
+ ```tsx
215
+ "use client";
216
+
217
+ import { useRef, useState, useCallback } from "react";
218
+ import { Thread } from "@/components/assistant-ui/thread";
219
+ import {
220
+ AssistantRuntimeProvider,
221
+ Interactables,
222
+ Suggestions,
223
+ useAui,
224
+ useInteractable,
225
+ useAssistantTool,
226
+ } from "@assistant-ui/react";
227
+ import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
228
+ import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
229
+ import { z } from "zod";
230
+ import {
231
+ CheckCircle2Icon,
232
+ CircleIcon,
233
+ ListTodoIcon,
234
+ StickyNoteIcon,
235
+ Trash2Icon,
236
+ PlusIcon,
237
+ } from "lucide-react";
238
+
239
+ // ===========================================================================
240
+ // 1. Task Board — single-instance interactable + custom tool
241
+ // ===========================================================================
242
+
243
+ type Task = { id: string; title: string; done: boolean };
244
+ type TaskBoardState = { tasks: Task[] };
245
+
246
+ const taskBoardSchema = z.object({
247
+ tasks: z.array(
248
+ z.object({
249
+ id: z.string(),
250
+ title: z.string(),
251
+ done: z.boolean(),
252
+ }),
253
+ ),
254
+ });
255
+
256
+ const taskBoardInitialState: TaskBoardState = { tasks: [] };
257
+
258
+ let nextTaskId = 0;
259
+
260
+ function TaskBoard() {
261
+ const [state, setState] = useInteractable<TaskBoardState>("taskBoard", {
262
+ description:
263
+ "A task board showing the user's tasks. Use the manage_tasks tool (not update_taskBoard) to add/toggle/remove/clear tasks.",
264
+ stateSchema: taskBoardSchema,
265
+ initialState: taskBoardInitialState,
266
+ });
267
+
268
+ const setStateRef = useRef(setState);
269
+ setStateRef.current = setState;
270
+
271
+ useAssistantTool({
272
+ toolName: "manage_tasks",
273
+ description:
274
+ 'Manage tasks on the task board. Actions: "add" (requires title), "toggle" (requires id), "remove" (requires id), "clear" (no extra fields).',
275
+ parameters: z.object({
276
+ action: z.enum(["add", "toggle", "remove", "clear"]),
277
+ title: z.string().optional(),
278
+ id: z.string().optional(),
279
+ }),
280
+ execute: async (args) => {
281
+ const set = setStateRef.current;
282
+ switch (args.action) {
283
+ case "add": {
284
+ const id = `task-${++nextTaskId}`;
285
+ set((prev) => ({
286
+ tasks: [
287
+ ...prev.tasks,
288
+ { id, title: args.title ?? "Untitled", done: false },
289
+ ],
290
+ }));
291
+ return { success: true, id };
292
+ }
293
+ case "toggle": {
294
+ if (!args.id) return { success: false, error: "id is required" };
295
+ set((prev) => ({
296
+ tasks: prev.tasks.map((t) =>
297
+ t.id === args.id ? { ...t, done: !t.done } : t,
298
+ ),
299
+ }));
300
+ return { success: true };
301
+ }
302
+ case "remove": {
303
+ if (!args.id) return { success: false, error: "id is required" };
304
+ set((prev) => ({
305
+ tasks: prev.tasks.filter((t) => t.id !== args.id),
306
+ }));
307
+ return { success: true };
308
+ }
309
+ case "clear": {
310
+ set({ tasks: [] });
311
+ return { success: true };
312
+ }
313
+ default:
314
+ return { success: false, error: "Unknown action" };
315
+ }
316
+ },
317
+ });
318
+
319
+ const doneCount = state.tasks.filter((t) => t.done).length;
320
+
321
+ return (
322
+ <div className="flex flex-col">
323
+ <div className="flex items-center gap-2 border-b px-4 py-3">
324
+ <ListTodoIcon className="size-4 text-muted-foreground" />
325
+ <span className="font-semibold text-sm">Task Board</span>
326
+ {state.tasks.length > 0 && (
327
+ <span className="ml-auto rounded-full bg-primary/10 px-2 py-0.5 font-medium text-primary text-xs">
328
+ {doneCount}/{state.tasks.length}
329
+ </span>
330
+ )}
331
+ </div>
332
+ <div className="flex-1 overflow-y-auto p-3">
333
+ {state.tasks.length === 0 ? (
334
+ <p className="py-6 text-center text-muted-foreground text-xs">
335
+ No tasks yet. Ask the assistant!
336
+ </p>
337
+ ) : (
338
+ <ul className="space-y-1">
339
+ {state.tasks.map((task) => (
340
+ <li
341
+ key={task.id}
342
+ className="group flex items-center gap-2 rounded-lg px-3 py-2 transition-colors hover:bg-muted"
343
+ >
344
+ <button
345
+ type="button"
346
+ onClick={() =>
347
+ setState((prev) => ({
348
+ tasks: prev.tasks.map((t) =>
349
+ t.id === task.id ? { ...t, done: !t.done } : t,
350
+ ),
351
+ }))
352
+ }
353
+ className="shrink-0"
354
+ >
355
+ {task.done ? (
356
+ <CheckCircle2Icon className="size-4 text-primary" />
357
+ ) : (
358
+ <CircleIcon className="size-4 text-muted-foreground" />
359
+ )}
360
+ </button>
361
+ <span
362
+ className={`flex-1 text-sm ${task.done ? "text-muted-foreground line-through" : ""}`}
363
+ >
364
+ {task.title}
365
+ </span>
366
+ <button
367
+ type="button"
368
+ onClick={() =>
369
+ setState((prev) => ({
370
+ tasks: prev.tasks.filter((t) => t.id !== task.id),
371
+ }))
372
+ }
373
+ className="shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
374
+ >
375
+ <Trash2Icon className="size-3.5 text-muted-foreground hover:text-destructive" />
376
+ </button>
377
+ </li>
378
+ ))}
379
+ </ul>
380
+ )}
381
+ </div>
382
+ </div>
383
+ );
384
+ }
385
+
386
+ // ===========================================================================
387
+ // 2. Sticky Notes — multi-instance interactable + selection + partial updates
388
+ // ===========================================================================
389
+
390
+ type NoteState = { title: string; content: string; color: string };
391
+
392
+ const noteSchema = z.object({
393
+ title: z.string(),
394
+ content: z.string(),
395
+ color: z.enum(["yellow", "blue", "green", "pink"]),
396
+ });
397
+
398
+ const noteInitialState: NoteState = {
399
+ title: "New Note",
400
+ content: "",
401
+ color: "yellow",
402
+ };
403
+
404
+ const COLORS: Record<string, string> = {
405
+ yellow: "bg-yellow-100 border-yellow-300",
406
+ blue: "bg-blue-100 border-blue-300",
407
+ green: "bg-green-100 border-green-300",
408
+ pink: "bg-pink-100 border-pink-300",
409
+ };
410
+
411
+ function NoteCard({
412
+ noteId,
413
+ selectedId,
414
+ onSelect,
415
+ onRemove,
416
+ }: {
417
+ noteId: string;
418
+ selectedId: string | null;
419
+ onSelect: (id: string) => void;
420
+ onRemove: (id: string) => void;
421
+ }) {
422
+ // Multi-instance: each NoteCard has a unique `id`, all share name "note"
423
+ // Partial updates: AI can send { color: "blue" } without resending title/content
424
+ // Selection: clicking a note calls setSelected(true)
425
+ const [state, , { setSelected }] = useInteractable<NoteState>("note", {
426
+ id: noteId,
427
+ description:
428
+ "A sticky note. The AI can partially update any field (title, content, color) without resending the others.",
429
+ stateSchema: noteSchema,
430
+ initialState: noteInitialState,
431
+ selected: selectedId === noteId,
432
+ });
433
+
434
+ const isSelected = selectedId === noteId;
435
+
436
+ const handleClick = () => {
437
+ onSelect(noteId);
438
+ setSelected(true);
439
+ };
440
+
441
+ return (
442
+ <div
443
+ role="button"
444
+ tabIndex={0}
445
+ onClick={handleClick}
446
+ onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && handleClick()}
447
+ className={`group relative flex w-full cursor-pointer flex-col gap-1 rounded-lg border-2 p-3 text-left transition-all ${COLORS[state.color] ?? COLORS.yellow} ${isSelected ? "ring-2 ring-primary ring-offset-1" : "hover:shadow-sm"}`}
448
+ >
449
+ {isSelected && (
450
+ <span className="absolute top-1.5 right-2 rounded bg-primary px-1.5 py-0.5 font-medium text-[10px] text-primary-foreground leading-none">
451
+ SELECTED
452
+ </span>
453
+ )}
454
+ <span className="pr-16 font-semibold text-sm text-zinc-800">
455
+ {state.title}
456
+ </span>
457
+ <span className="line-clamp-3 text-xs text-zinc-600">
458
+ {state.content || "Empty note"}
459
+ </span>
460
+ <button
461
+ type="button"
462
+ onClick={(e) => {
463
+ e.stopPropagation();
464
+ onRemove(noteId);
465
+ }}
466
+ className="absolute right-2 bottom-2 rounded p-0.5 opacity-0 transition-opacity hover:bg-black/10 group-hover:opacity-100"
467
+ >
468
+ <Trash2Icon className="size-3 text-zinc-500" />
469
+ </button>
470
+ </div>
471
+ );
472
+ }
473
+
474
+ function NotesPanel() {
475
+ const [noteIds, setNoteIds] = useState<string[]>([]);
476
+ const [selectedId, setSelectedId] = useState<string | null>(null);
477
+
478
+ const noteIdsRef = useRef(noteIds);
479
+ noteIdsRef.current = noteIds;
480
+ const setNoteIdsRef = useRef(setNoteIds);
481
+ setNoteIdsRef.current = setNoteIds;
482
+ const setSelectedIdRef = useRef(setSelectedId);
483
+ setSelectedIdRef.current = setSelectedId;
484
+
485
+ // Tool to add/remove notes (manages the list, not the note content)
486
+ useAssistantTool({
487
+ toolName: "manage_notes",
488
+ description:
489
+ 'Manage sticky notes. Actions: "add" (creates a new note, returns its id), "remove" (requires noteId), "clear" (removes all notes). After adding, use the update_note_{id} tool to set its content.',
490
+ parameters: z.object({
491
+ action: z.enum(["add", "remove", "clear"]),
492
+ noteId: z.string().optional(),
493
+ }),
494
+ execute: async (args) => {
495
+ switch (args.action) {
496
+ case "add": {
497
+ const id = `note-${Date.now().toString(36)}`;
498
+ setNoteIdsRef.current((prev) => [...prev, id]);
499
+ return { success: true, noteId: id };
500
+ }
501
+ case "remove": {
502
+ if (args.noteId) {
503
+ setNoteIdsRef.current((prev) =>
504
+ prev.filter((id) => id !== args.noteId),
505
+ );
506
+ }
507
+ return { success: true };
508
+ }
509
+ case "clear": {
510
+ setNoteIdsRef.current([]);
511
+ setSelectedIdRef.current(null);
512
+ return { success: true };
513
+ }
514
+ default:
515
+ return { success: false, error: "Unknown action" };
516
+ }
517
+ },
518
+ });
519
+
520
+ const handleSelect = useCallback((id: string) => {
521
+ setSelectedId(id);
522
+ }, []);
523
+
524
+ const handleRemove = useCallback((id: string) => {
525
+ setNoteIds((prev) => prev.filter((n) => n !== id));
526
+ setSelectedId((prev) => (prev === id ? null : prev));
527
+ }, []);
528
+
529
+ return (
530
+ <div className="flex flex-col">
531
+ <div className="flex items-center gap-2 border-b px-4 py-3">
532
+ <StickyNoteIcon className="size-4 text-muted-foreground" />
533
+ <span className="font-semibold text-sm">Notes</span>
534
+ <span className="ml-auto text-muted-foreground text-xs">
535
+ {noteIds.length}
536
+ </span>
537
+ <button
538
+ type="button"
539
+ onClick={() => {
540
+ const id = `note-${Date.now().toString(36)}`;
541
+ setNoteIds((prev) => [...prev, id]);
542
+ }}
543
+ className="rounded p-1 transition-colors hover:bg-muted"
544
+ >
545
+ <PlusIcon className="size-3.5 text-muted-foreground" />
546
+ </button>
547
+ </div>
548
+ <div className="flex-1 overflow-y-auto p-3">
549
+ {noteIds.length === 0 ? (
550
+ <p className="py-6 text-center text-muted-foreground text-xs">
551
+ No notes yet. Ask the assistant!
552
+ </p>
553
+ ) : (
554
+ <div className="grid gap-2">
555
+ {noteIds.map((noteId) => (
556
+ <NoteCard
557
+ key={noteId}
558
+ noteId={noteId}
559
+ selectedId={selectedId}
560
+ onSelect={handleSelect}
561
+ onRemove={handleRemove}
562
+ />
563
+ ))}
564
+ </div>
565
+ )}
566
+ </div>
567
+ </div>
568
+ );
569
+ }
570
+
571
+ // ===========================================================================
572
+ // App
573
+ // ===========================================================================
574
+
575
+ export default function Home() {
576
+ const runtime = useChatRuntime({
577
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
578
+ });
579
+
580
+ const aui = useAui({
581
+ interactables: Interactables(),
582
+ suggestions: Suggestions([
583
+ {
584
+ title: "Add 3 tasks",
585
+ label: "for a grocery run",
586
+ prompt: "Add 3 tasks for a grocery run",
587
+ },
588
+ {
589
+ title: "Create 2 notes",
590
+ label: "and set different colors",
591
+ prompt:
592
+ "Create 2 sticky notes: one blue note about meeting prep, and one green note about project ideas",
593
+ },
594
+ {
595
+ title: "Change selected note",
596
+ label: "to pink color",
597
+ prompt: "Change the selected note's color to pink",
598
+ },
599
+ ]),
600
+ });
601
+
602
+ return (
603
+ <AssistantRuntimeProvider aui={aui} runtime={runtime}>
604
+ <main className="flex h-full">
605
+ <div className="flex-1">
606
+ <Thread />
607
+ </div>
608
+ <div className="flex w-80 flex-col border-l">
609
+ <div className="flex-1 overflow-y-auto">
610
+ <NotesPanel />
611
+ </div>
612
+ <div className="flex-1 overflow-y-auto border-t">
613
+ <TaskBoard />
614
+ </div>
615
+ </div>
616
+ </main>
617
+ </AssistantRuntimeProvider>
618
+ );
619
+ }
620
+
621
+ ```
622
+
623
+ ## components.json
624
+
625
+ ```json
626
+ {
627
+ "$schema": "https://ui.shadcn.com/schema.json",
628
+ "style": "new-york",
629
+ "rsc": true,
630
+ "tsx": true,
631
+ "tailwind": {
632
+ "config": "",
633
+ "css": "app/globals.css",
634
+ "baseColor": "zinc",
635
+ "cssVariables": true,
636
+ "prefix": ""
637
+ },
638
+ "aliases": {
639
+ "components": "@/components",
640
+ "utils": "@/lib/utils",
641
+ "ui": "@/components/ui",
642
+ "lib": "@/lib",
643
+ "hooks": "@/hooks"
644
+ },
645
+ "iconLibrary": "lucide",
646
+ "registries": {
647
+ "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
648
+ }
649
+ }
650
+
651
+ ```
652
+
653
+ ## next.config.ts
654
+
655
+ ```typescript
656
+ import type { NextConfig } from "next";
657
+
658
+ const nextConfig: NextConfig = {
659
+ /* config options here */
660
+ };
661
+
662
+ export default nextConfig;
663
+
664
+ ```
665
+
666
+ ## package.json
667
+
668
+ ```json
669
+ {
670
+ "name": "with-interactables",
671
+ "version": "0.0.0",
672
+ "private": true,
673
+ "type": "module",
674
+ "scripts": {
675
+ "dev": "next dev",
676
+ "build": "next build",
677
+ "start": "next start"
678
+ },
679
+ "dependencies": {
680
+ "@ai-sdk/openai": "^3.0.48",
681
+ "@assistant-ui/react": "workspace:*",
682
+ "@assistant-ui/react-ai-sdk": "workspace:*",
683
+ "@assistant-ui/ui": "workspace:*",
684
+ "ai": "^6.0.138",
685
+ "class-variance-authority": "^0.7.1",
686
+ "clsx": "^2.1.1",
687
+ "lucide-react": "^1.7.0",
688
+ "next": "^16.2.1",
689
+ "react": "^19.2.4",
690
+ "react-dom": "^19.2.4",
691
+ "tailwind-merge": "^3.5.0",
692
+ "zod": "^4.3.6"
693
+ },
694
+ "devDependencies": {
695
+ "@assistant-ui/x-buildutils": "workspace:*",
696
+ "@tailwindcss/postcss": "^4.2.2",
697
+ "@types/node": "^25.5.0",
698
+ "@types/react": "^19.2.14",
699
+ "@types/react-dom": "^19.2.3",
700
+ "postcss": "^8.5.8",
701
+ "tailwindcss": "^4.2.2",
702
+ "tw-animate-css": "^1.4.0",
703
+ "typescript": "5.9.3"
704
+ }
705
+ }
706
+
707
+ ```
708
+
709
+ ## README.md
710
+
711
+ ```markdown
712
+ # with-interactables
713
+
714
+ Demonstrates **interactable components** — persistent UI components whose state can be read and updated by both the user and the AI assistant.
715
+
716
+ ## Features Demonstrated
717
+
718
+ ### Task Board (single instance + custom tool)
719
+ - `useInteractable("taskBoard", config)` — registers a single interactable
720
+ - `useAssistantTool("manage_tasks")` — custom tool for incremental add/toggle/remove/clear
721
+ - Auto-generated `update_taskBoard` tool with **partial updates** (AI only sends changed fields)
722
+
723
+ ### Sticky Notes (multi-instance + selection + partial updates)
724
+ - Multiple `<NoteCard>` components each call `useInteractable("note", { id: noteId, ... })`
725
+ - **Multi-instance**: each note gets its own `update_note_{id}` tool automatically
726
+ - **Selection**: click a note to select it; AI sees `(SELECTED)` in system prompt and prioritizes it
727
+ - **Partial updates**: AI can change just `{ color: "pink" }` without resending title and content
728
+
729
+ ## Getting Started
730
+
731
+ ```bash
732
+ # Install dependencies (from monorepo root)
733
+ pnpm install
734
+
735
+ # Set your OpenAI API key
736
+ cp .env.example .env.local
737
+ # Edit .env.local and add your OPENAI_API_KEY
738
+
739
+ # Run the development server
740
+ pnpm --filter with-interactables dev
741
+ ```
742
+
743
+ Open [http://localhost:3000](http://localhost:3000) to see the example.
744
+
745
+ ## Key Concepts
746
+
747
+ - **`Interactables()`** — scope resource registered via `useAui`
748
+ - **`useInteractable(name, config)`** — returns `[state, setState, { id, setSelected }]`
749
+ - **Partial updates** — auto-generated tools use partial schemas; AI only sends changed fields
750
+ - **Multi-instance** — same `name`, different `id`; tools named `update_{name}_{id}`
751
+ - **Selection** — `setSelected(true)` marks a component as focused for the AI
752
+ - **`useAssistantTool`** — custom frontend tools for fine-grained control
753
+ - **`sendAutomaticallyWhen`** — auto-sends follow-up messages when tool calls complete
754
+
755
+ ```
756
+
757
+ ## tsconfig.json
758
+
759
+ ```json
760
+ {
761
+ "extends": "@assistant-ui/x-buildutils/ts/next",
762
+ "compilerOptions": {
763
+ "paths": {
764
+ "@/*": ["./*"],
765
+ "@/components/assistant-ui/*": [
766
+ "../../packages/ui/src/components/assistant-ui/*"
767
+ ],
768
+ "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
769
+ "@/lib/utils": ["../../packages/ui/src/lib/utils"],
770
+ "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
771
+ }
772
+ },
773
+ "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
774
+ "exclude": ["node_modules"]
775
+ }
776
+
777
+ ```
778
+