@assistant-ui/mcp-docs-server 0.1.32 → 0.1.33

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 (112) hide show
  1. package/.docs/organized/code-examples/waterfall.md +15 -17
  2. package/.docs/organized/code-examples/with-a2a.md +19 -19
  3. package/.docs/organized/code-examples/with-ag-ui.md +25 -20
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +6 -4
  5. package/.docs/organized/code-examples/with-artifacts.md +34 -28
  6. package/.docs/organized/code-examples/with-assistant-transport.md +59 -47
  7. package/.docs/organized/code-examples/with-browser-extension.md +17 -5
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +376 -78
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +20 -19
  10. package/.docs/organized/code-examples/with-cloud.md +6 -4
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +9 -7
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +18 -16
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +9 -8
  14. package/.docs/organized/code-examples/with-expo.md +23 -17
  15. package/.docs/organized/code-examples/with-external-store.md +4 -2
  16. package/.docs/organized/code-examples/with-ffmpeg.md +317 -280
  17. package/.docs/organized/code-examples/with-generative-ui.md +1018 -214
  18. package/.docs/organized/code-examples/with-google-adk.md +4 -2
  19. package/.docs/organized/code-examples/with-heat-graph.md +8 -6
  20. package/.docs/organized/code-examples/with-image-generation.md +19 -17
  21. package/.docs/organized/code-examples/with-interactables.md +312 -234
  22. package/.docs/organized/code-examples/with-langchain.md +9 -7
  23. package/.docs/organized/code-examples/with-langgraph.md +82 -78
  24. package/.docs/organized/code-examples/with-livekit.md +19 -17
  25. package/.docs/organized/code-examples/with-mcp.md +13 -7
  26. package/.docs/organized/code-examples/with-opencode.md +61 -61
  27. package/.docs/organized/code-examples/with-react-hook-form.md +14 -12
  28. package/.docs/organized/code-examples/with-react-ink.md +2 -2
  29. package/.docs/organized/code-examples/with-react-router.md +11 -9
  30. package/.docs/organized/code-examples/with-resumable-stream.md +10 -9
  31. package/.docs/organized/code-examples/with-store.md +14 -12
  32. package/.docs/organized/code-examples/with-tanstack.md +8 -9
  33. package/.docs/organized/code-examples/with-tap-runtime.md +21 -19
  34. package/.docs/raw/docs/(docs)/architecture.mdx +43 -2
  35. package/.docs/raw/docs/(docs)/cli.mdx +1 -2
  36. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +34 -26
  37. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +32 -26
  38. package/.docs/raw/docs/(docs)/installation.mdx +1 -1
  39. package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +5 -1
  40. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +14 -14
  41. package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +11 -1
  42. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +18 -0
  43. package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +33 -0
  44. package/.docs/raw/docs/(reference)/api-reference/generative-ui/spec.mdx +45 -0
  45. package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +41 -41
  46. package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +19 -1
  47. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +122 -122
  48. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +32 -2
  49. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +6 -0
  50. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +4 -6
  51. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +30 -0
  52. package/.docs/raw/docs/(reference)/api-reference/tools/component-tools.mdx +20 -3
  53. package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +8 -8
  54. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +52 -4
  55. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +6 -7
  56. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +58 -46
  57. package/.docs/raw/docs/cloud/ai-sdk.mdx +0 -2
  58. package/.docs/raw/docs/guides/chain-of-thought.mdx +7 -9
  59. package/.docs/raw/docs/guides/context-api.mdx +2 -1
  60. package/.docs/raw/docs/guides/index.mdx +3 -12
  61. package/.docs/raw/docs/guides/mentions.mdx +4 -4
  62. package/.docs/raw/docs/guides/slash-commands.mdx +1 -1
  63. package/.docs/raw/docs/guides/suggestions.mdx +1 -1
  64. package/.docs/raw/docs/ink/hooks.mdx +98 -85
  65. package/.docs/raw/docs/ink/migration.mdx +1 -1
  66. package/.docs/raw/docs/ink/primitives.mdx +2 -2
  67. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +1 -1
  68. package/.docs/raw/docs/integrations/index.mdx +2 -2
  69. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +17 -2
  70. package/.docs/raw/docs/migrations/toolkit-tools.mdx +226 -0
  71. package/.docs/raw/docs/primitives/chain-of-thought.mdx +10 -16
  72. package/.docs/raw/docs/primitives/message.mdx +9 -10
  73. package/.docs/raw/docs/react-native/hooks.mdx +57 -82
  74. package/.docs/raw/docs/react-native/migration.mdx +1 -1
  75. package/.docs/raw/docs/react-native/primitives.mdx +2 -2
  76. package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +3 -0
  77. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +122 -1
  78. package/.docs/raw/docs/runtimes/concepts/threads.mdx +7 -1
  79. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +18 -7
  80. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +108 -38
  81. package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +2 -2
  82. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +1 -1
  83. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +64 -50
  84. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +98 -86
  85. package/.docs/raw/docs/tools/backend.mdx +136 -0
  86. package/.docs/raw/docs/tools/defining-tools.mdx +413 -0
  87. package/.docs/raw/docs/tools/dynamic-tools.mdx +110 -0
  88. package/.docs/raw/docs/tools/generative-ui.mdx +214 -0
  89. package/.docs/raw/docs/tools/index.mdx +76 -0
  90. package/.docs/raw/docs/{guides → tools}/interactables.mdx +1 -1
  91. package/.docs/raw/docs/{integrations/tools → tools}/mcp.mdx +77 -50
  92. package/.docs/raw/docs/{guides → tools}/multi-agent.mdx +17 -19
  93. package/.docs/raw/docs/tools/tool-ui.mdx +967 -0
  94. package/.docs/raw/docs/{integrations/tools/react-mcp.mdx → tools/user-managed-mcp.mdx} +3 -3
  95. package/.docs/raw/docs/ui/directive-text.mdx +3 -3
  96. package/.docs/raw/docs/ui/mcp-config.mdx +4 -4
  97. package/.docs/raw/docs/ui/part-grouping.mdx +84 -50
  98. package/.docs/raw/docs/ui/reasoning.mdx +4 -5
  99. package/.docs/raw/docs/ui/tool-group.mdx +5 -6
  100. package/.docs/raw/docs/utilities/heat-graph.mdx +1 -1
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/prepare-docs/code-examples.d.ts.map +1 -1
  103. package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
  104. package/dist/utils/paths.d.ts.map +1 -1
  105. package/package.json +3 -3
  106. package/.docs/organized/code-examples/with-parent-id-grouping.md +0 -596
  107. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +0 -151
  108. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +0 -230
  109. package/.docs/raw/docs/guides/generative-ui.mdx +0 -142
  110. package/.docs/raw/docs/guides/tool-ui.mdx +0 -858
  111. package/.docs/raw/docs/guides/tools.mdx +0 -736
  112. /package/.docs/raw/docs/{guides → tools}/mcp-apps.mdx +0 -0
@@ -29,7 +29,7 @@ export async function POST(req: Request) {
29
29
 
30
30
  // Convert client-defined tools (forwarded from model context) to AI SDK format.
31
31
  // These have no `execute` — they are frontend tools executed on the client
32
- // via useAssistantTool / useAssistantInteractable.
32
+ // via Tools({ toolkit }) / useAssistantInteractable.
33
33
  const tools = clientTools
34
34
  ? Object.fromEntries(
35
35
  Object.entries(clientTools).map(([name, def]) => [
@@ -214,20 +214,28 @@ export default function RootLayout({
214
214
  ```tsx
215
215
  "use client";
216
216
 
217
- import { useRef, useState, useCallback, useEffect } from "react";
217
+ import {
218
+ useRef,
219
+ useState,
220
+ useCallback,
221
+ useEffect,
222
+ type Dispatch,
223
+ type SetStateAction,
224
+ } from "react";
218
225
  import { Thread } from "@/components/assistant-ui/thread";
219
226
  import {
220
227
  AssistantRuntimeProvider,
228
+ AuiProvider,
221
229
  Interactables,
222
230
  Suggestions,
231
+ Tools,
223
232
  useAui,
233
+ useAuiToolOverrides,
224
234
  useAssistantInteractable,
225
235
  useInteractableState,
226
- useAssistantTool,
227
236
  } from "@assistant-ui/react";
228
237
  import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
229
238
  import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
230
- import { z } from "zod";
231
239
  import {
232
240
  CheckCircle2Icon,
233
241
  CircleIcon,
@@ -237,23 +245,15 @@ import {
237
245
  Trash2Icon,
238
246
  PlusIcon,
239
247
  } from "lucide-react";
240
-
241
- type Task = { id: string; title: string; done: boolean };
242
- type TaskBoardState = { tasks: Task[] };
243
-
244
- const taskBoardSchema = z.object({
245
- tasks: z.array(
246
- z.object({
247
- id: z.string(),
248
- title: z.string(),
249
- done: z.boolean(),
250
- }),
251
- ),
252
- });
253
-
254
- const taskBoardInitialState: TaskBoardState = { tasks: [] };
255
-
256
- let nextTaskId = 0;
248
+ import {
249
+ type NoteState,
250
+ type TaskBoardState,
251
+ noteInitialState,
252
+ noteSchema,
253
+ taskBoardInitialState,
254
+ taskBoardSchema,
255
+ } from "./state";
256
+ import toolkit from "./toolkits";
257
257
 
258
258
  function TaskBoard() {
259
259
  const id = useAssistantInteractable("taskBoard", {
@@ -267,140 +267,128 @@ function TaskBoard() {
267
267
  taskBoardInitialState,
268
268
  );
269
269
 
270
- const setStateRef = useRef(setState);
271
- setStateRef.current = setState;
272
-
273
- useAssistantTool({
274
- toolName: "manage_tasks",
275
- description:
276
- 'Manage tasks on the task board. Actions: "add" (requires title), "toggle" (requires id), "remove" (requires id), "clear" (no extra fields).',
277
- parameters: z.object({
278
- action: z.enum(["add", "toggle", "remove", "clear"]),
279
- title: z.string().optional(),
280
- id: z.string().optional(),
281
- }),
282
- execute: async (args) => {
283
- const set = setStateRef.current;
284
- switch (args.action) {
285
- case "add": {
286
- const id = `task-${++nextTaskId}`;
287
- set((prev) => ({
288
- tasks: [
289
- ...prev.tasks,
290
- { id, title: args.title ?? "Untitled", done: false },
291
- ],
292
- }));
293
- return { success: true, id };
294
- }
295
- case "toggle": {
296
- if (!args.id) return { success: false, error: "id is required" };
297
- set((prev) => ({
298
- tasks: prev.tasks.map((t) =>
299
- t.id === args.id ? { ...t, done: !t.done } : t,
300
- ),
301
- }));
302
- return { success: true };
303
- }
304
- case "remove": {
305
- if (!args.id) return { success: false, error: "id is required" };
306
- set((prev) => ({
307
- tasks: prev.tasks.filter((t) => t.id !== args.id),
308
- }));
309
- return { success: true };
310
- }
311
- case "clear": {
312
- set({ tasks: [] });
313
- return { success: true };
314
- }
315
- default:
316
- return { success: false, error: "Unknown action" };
317
- }
318
- },
319
- });
270
+ const aui = useAui({ tools: Tools({ toolkit }) });
320
271
 
321
272
  const doneCount = state.tasks.filter((t) => t.done).length;
322
273
 
323
274
  return (
324
- <div className="flex flex-col">
325
- <div className="flex items-center gap-2 border-b px-4 py-3">
326
- <ListTodoIcon className="size-4 text-muted-foreground" />
327
- <span className="font-semibold text-sm">Task Board</span>
328
- {isPending && (
329
- <Loader2Icon className="size-3 animate-spin text-muted-foreground" />
330
- )}
331
- {state.tasks.length > 0 && (
332
- <span className="ml-auto rounded-full bg-primary/10 px-2 py-0.5 font-medium text-primary text-xs">
333
- {doneCount}/{state.tasks.length}
334
- </span>
335
- )}
336
- </div>
337
- <div className="flex-1 overflow-y-auto p-3">
338
- {state.tasks.length === 0 ? (
339
- <p className="py-6 text-center text-muted-foreground text-xs">
340
- No tasks yet. Ask the assistant!
341
- </p>
342
- ) : (
343
- <ul className="space-y-1">
344
- {state.tasks.map((task) => (
345
- <li
346
- key={task.id}
347
- className="group flex items-center gap-2 rounded-lg px-3 py-2 transition-colors hover:bg-muted"
348
- >
349
- <button
350
- type="button"
351
- onClick={() =>
352
- setState((prev) => ({
353
- tasks: prev.tasks.map((t) =>
354
- t.id === task.id ? { ...t, done: !t.done } : t,
355
- ),
356
- }))
357
- }
358
- className="shrink-0"
359
- >
360
- {task.done ? (
361
- <CheckCircle2Icon className="size-4 text-primary" />
362
- ) : (
363
- <CircleIcon className="size-4 text-muted-foreground" />
364
- )}
365
- </button>
366
- <span
367
- className={`flex-1 text-sm ${task.done ? "text-muted-foreground line-through" : ""}`}
368
- >
369
- {task.title}
370
- </span>
371
- <button
372
- type="button"
373
- onClick={() =>
374
- setState((prev) => ({
375
- tasks: prev.tasks.filter((t) => t.id !== task.id),
376
- }))
377
- }
378
- className="shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
275
+ <AuiProvider value={aui}>
276
+ <TaskBoardToolOverrides setState={setState} />
277
+ <div className="flex flex-col">
278
+ <div className="flex items-center gap-2 border-b px-4 py-3">
279
+ <ListTodoIcon className="text-muted-foreground size-4" />
280
+ <span className="text-sm font-semibold">Task Board</span>
281
+ {isPending && (
282
+ <Loader2Icon className="text-muted-foreground size-3 animate-spin" />
283
+ )}
284
+ {state.tasks.length > 0 && (
285
+ <span className="bg-primary/10 text-primary ml-auto rounded-full px-2 py-0.5 text-xs font-medium">
286
+ {doneCount}/{state.tasks.length}
287
+ </span>
288
+ )}
289
+ </div>
290
+ <div className="flex-1 overflow-y-auto p-3">
291
+ {state.tasks.length === 0 ? (
292
+ <p className="text-muted-foreground py-6 text-center text-xs">
293
+ No tasks yet. Ask the assistant!
294
+ </p>
295
+ ) : (
296
+ <ul className="space-y-1">
297
+ {state.tasks.map((task) => (
298
+ <li
299
+ key={task.id}
300
+ className="group hover:bg-muted flex items-center gap-2 rounded-lg px-3 py-2 transition-colors"
379
301
  >
380
- <Trash2Icon className="size-3.5 text-muted-foreground hover:text-destructive" />
381
- </button>
382
- </li>
383
- ))}
384
- </ul>
385
- )}
302
+ <button
303
+ type="button"
304
+ onClick={() =>
305
+ setState((prev) => ({
306
+ tasks: prev.tasks.map((t) =>
307
+ t.id === task.id ? { ...t, done: !t.done } : t,
308
+ ),
309
+ }))
310
+ }
311
+ className="shrink-0"
312
+ >
313
+ {task.done ? (
314
+ <CheckCircle2Icon className="text-primary size-4" />
315
+ ) : (
316
+ <CircleIcon className="text-muted-foreground size-4" />
317
+ )}
318
+ </button>
319
+ <span
320
+ className={`flex-1 text-sm ${task.done ? "text-muted-foreground line-through" : ""}`}
321
+ >
322
+ {task.title}
323
+ </span>
324
+ <button
325
+ type="button"
326
+ onClick={() =>
327
+ setState((prev) => ({
328
+ tasks: prev.tasks.filter((t) => t.id !== task.id),
329
+ }))
330
+ }
331
+ className="shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
332
+ >
333
+ <Trash2Icon className="text-muted-foreground hover:text-destructive size-3.5" />
334
+ </button>
335
+ </li>
336
+ ))}
337
+ </ul>
338
+ )}
339
+ </div>
386
340
  </div>
387
- </div>
341
+ </AuiProvider>
388
342
  );
389
343
  }
390
344
 
391
- type NoteState = { title: string; content: string; color: string };
392
-
393
- const noteSchema = z.object({
394
- title: z.string(),
395
- content: z.string(),
396
- color: z.enum(["yellow", "blue", "green", "pink"]),
397
- });
398
-
399
- const noteInitialState: NoteState = {
400
- title: "New Note",
401
- content: "",
402
- color: "yellow",
403
- };
345
+ function TaskBoardToolOverrides({
346
+ setState,
347
+ }: {
348
+ setState: Dispatch<SetStateAction<TaskBoardState>>;
349
+ }) {
350
+ useAuiToolOverrides({
351
+ manage_tasks: {
352
+ execute: async (args) => {
353
+ switch (args.action) {
354
+ case "add": {
355
+ const id = crypto.randomUUID();
356
+ setState((prev) => ({
357
+ tasks: [
358
+ ...prev.tasks,
359
+ { id, title: args.title ?? "Untitled", done: false },
360
+ ],
361
+ }));
362
+ return { success: true, id };
363
+ }
364
+ case "toggle": {
365
+ if (!args.id) return { success: false, error: "id is required" };
366
+ setState((prev) => ({
367
+ tasks: prev.tasks.map((t) =>
368
+ t.id === args.id ? { ...t, done: !t.done } : t,
369
+ ),
370
+ }));
371
+ return { success: true };
372
+ }
373
+ case "remove": {
374
+ if (!args.id) return { success: false, error: "id is required" };
375
+ setState((prev) => ({
376
+ tasks: prev.tasks.filter((t) => t.id !== args.id),
377
+ }));
378
+ return { success: true };
379
+ }
380
+ case "clear": {
381
+ setState({ tasks: [] });
382
+ return { success: true };
383
+ }
384
+ default:
385
+ return { success: false, error: "Unknown action" };
386
+ }
387
+ },
388
+ },
389
+ });
390
+ return null;
391
+ }
404
392
 
405
393
  const COLORS: Record<string, string> = {
406
394
  yellow: "bg-yellow-100 border-yellow-300",
@@ -441,20 +429,19 @@ function NoteCard({
441
429
  };
442
430
 
443
431
  return (
444
- // biome-ignore lint/a11y/useSemanticElements: styled interactive card
445
432
  <div
446
433
  role="button"
447
434
  tabIndex={0}
448
435
  onClick={handleClick}
449
436
  onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && handleClick()}
450
- 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"}`}
437
+ 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-primary ring-2 ring-offset-1" : "hover:shadow-sm"}`}
451
438
  >
452
439
  {isSelected && (
453
- <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">
440
+ <span className="bg-primary text-primary-foreground absolute top-1.5 right-2 rounded px-1.5 py-0.5 text-[10px] leading-none font-medium">
454
441
  SELECTED
455
442
  </span>
456
443
  )}
457
- <span className="pr-16 font-semibold text-sm text-zinc-800">
444
+ <span className="pr-16 text-sm font-semibold text-zinc-800">
458
445
  {state.title}
459
446
  </span>
460
447
  <span className="line-clamp-3 text-xs text-zinc-600">
@@ -466,7 +453,7 @@ function NoteCard({
466
453
  e.stopPropagation();
467
454
  onRemove(noteId);
468
455
  }}
469
- className="absolute right-2 bottom-2 rounded p-0.5 opacity-0 transition-opacity hover:bg-black/10 group-hover:opacity-100"
456
+ className="absolute right-2 bottom-2 rounded p-0.5 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-black/10"
470
457
  >
471
458
  <Trash2Icon className="size-3 text-zinc-500" />
472
459
  </button>
@@ -500,46 +487,7 @@ function NotesPanel() {
500
487
  localStorage.setItem(NOTE_IDS_KEY, JSON.stringify(noteIds));
501
488
  }, [noteIds]);
502
489
 
503
- const noteIdsRef = useRef(noteIds);
504
- noteIdsRef.current = noteIds;
505
- const setNoteIdsRef = useRef(setNoteIds);
506
- setNoteIdsRef.current = setNoteIds;
507
- const setSelectedIdRef = useRef(setSelectedId);
508
- setSelectedIdRef.current = setSelectedId;
509
-
510
- useAssistantTool({
511
- toolName: "manage_notes",
512
- description:
513
- '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.',
514
- parameters: z.object({
515
- action: z.enum(["add", "remove", "clear"]),
516
- noteId: z.string().optional(),
517
- }),
518
- execute: async (args) => {
519
- switch (args.action) {
520
- case "add": {
521
- const id = `note-${Date.now().toString(36)}`;
522
- setNoteIdsRef.current((prev) => [...prev, id]);
523
- return { success: true, noteId: id };
524
- }
525
- case "remove": {
526
- if (args.noteId) {
527
- setNoteIdsRef.current((prev) =>
528
- prev.filter((id) => id !== args.noteId),
529
- );
530
- }
531
- return { success: true };
532
- }
533
- case "clear": {
534
- setNoteIdsRef.current([]);
535
- setSelectedIdRef.current(null);
536
- return { success: true };
537
- }
538
- default:
539
- return { success: false, error: "Unknown action" };
540
- }
541
- },
542
- });
490
+ const aui = useAui({ tools: Tools({ toolkit }) });
543
491
 
544
492
  const handleSelect = useCallback((id: string) => {
545
493
  setSelectedId(id);
@@ -551,47 +499,89 @@ function NotesPanel() {
551
499
  }, []);
552
500
 
553
501
  return (
554
- <div className="flex flex-col">
555
- <div className="flex items-center gap-2 border-b px-4 py-3">
556
- <StickyNoteIcon className="size-4 text-muted-foreground" />
557
- <span className="font-semibold text-sm">Notes</span>
558
- <span className="ml-auto text-muted-foreground text-xs">
559
- {noteIds.length}
560
- </span>
561
- <button
562
- type="button"
563
- onClick={() => {
564
- const id = `note-${Date.now().toString(36)}`;
565
- setNoteIds((prev) => [...prev, id]);
566
- }}
567
- className="rounded p-1 transition-colors hover:bg-muted"
568
- >
569
- <PlusIcon className="size-3.5 text-muted-foreground" />
570
- </button>
571
- </div>
572
- <div className="flex-1 overflow-y-auto p-3">
573
- {noteIds.length === 0 ? (
574
- <p className="py-6 text-center text-muted-foreground text-xs">
575
- No notes yet. Ask the assistant!
576
- </p>
577
- ) : (
578
- <div className="grid gap-2">
579
- {noteIds.map((noteId) => (
580
- <NoteCard
581
- key={noteId}
582
- noteId={noteId}
583
- selectedId={selectedId}
584
- onSelect={handleSelect}
585
- onRemove={handleRemove}
586
- />
587
- ))}
588
- </div>
589
- )}
502
+ <AuiProvider value={aui}>
503
+ <NotesToolOverrides
504
+ setNoteIds={setNoteIds}
505
+ setSelectedId={setSelectedId}
506
+ />
507
+ <div className="flex flex-col">
508
+ <div className="flex items-center gap-2 border-b px-4 py-3">
509
+ <StickyNoteIcon className="text-muted-foreground size-4" />
510
+ <span className="text-sm font-semibold">Notes</span>
511
+ <span className="text-muted-foreground ml-auto text-xs">
512
+ {noteIds.length}
513
+ </span>
514
+ <button
515
+ type="button"
516
+ onClick={() => {
517
+ const id = `note-${crypto.randomUUID()}`;
518
+ setNoteIds((prev) => [...prev, id]);
519
+ }}
520
+ className="hover:bg-muted rounded p-1 transition-colors"
521
+ >
522
+ <PlusIcon className="text-muted-foreground size-3.5" />
523
+ </button>
524
+ </div>
525
+ <div className="flex-1 overflow-y-auto p-3">
526
+ {noteIds.length === 0 ? (
527
+ <p className="text-muted-foreground py-6 text-center text-xs">
528
+ No notes yet. Ask the assistant!
529
+ </p>
530
+ ) : (
531
+ <div className="grid gap-2">
532
+ {noteIds.map((noteId) => (
533
+ <NoteCard
534
+ key={noteId}
535
+ noteId={noteId}
536
+ selectedId={selectedId}
537
+ onSelect={handleSelect}
538
+ onRemove={handleRemove}
539
+ />
540
+ ))}
541
+ </div>
542
+ )}
543
+ </div>
590
544
  </div>
591
- </div>
545
+ </AuiProvider>
592
546
  );
593
547
  }
594
548
 
549
+ function NotesToolOverrides({
550
+ setNoteIds,
551
+ setSelectedId,
552
+ }: {
553
+ setNoteIds: Dispatch<SetStateAction<string[]>>;
554
+ setSelectedId: Dispatch<SetStateAction<string | null>>;
555
+ }) {
556
+ useAuiToolOverrides({
557
+ manage_notes: {
558
+ execute: async (args) => {
559
+ switch (args.action) {
560
+ case "add": {
561
+ const id = `note-${crypto.randomUUID()}`;
562
+ setNoteIds((prev) => [...prev, id]);
563
+ return { success: true, noteId: id };
564
+ }
565
+ case "remove": {
566
+ if (args.noteId) {
567
+ setNoteIds((prev) => prev.filter((id) => id !== args.noteId));
568
+ }
569
+ return { success: true };
570
+ }
571
+ case "clear": {
572
+ setNoteIds([]);
573
+ setSelectedId(null);
574
+ return { success: true };
575
+ }
576
+ default:
577
+ return { success: false, error: "Unknown action" };
578
+ }
579
+ },
580
+ },
581
+ });
582
+ return null;
583
+ }
584
+
595
585
  const STORAGE_KEY = "interactables-example";
596
586
 
597
587
  function useInteractablePersistence(aui: ReturnType<typeof useAui>) {
@@ -663,6 +653,92 @@ export default function Home() {
663
653
 
664
654
  ```
665
655
 
656
+ ## app/state.ts
657
+
658
+ ```typescript
659
+ import { z } from "zod";
660
+
661
+ export type Task = { id: string; title: string; done: boolean };
662
+ export type TaskBoardState = { tasks: Task[] };
663
+
664
+ export const taskBoardSchema = z.object({
665
+ tasks: z.array(
666
+ z.object({
667
+ id: z.string(),
668
+ title: z.string(),
669
+ done: z.boolean(),
670
+ }),
671
+ ),
672
+ });
673
+
674
+ export const taskBoardInitialState: TaskBoardState = { tasks: [] };
675
+
676
+ export const manageTasksParameters = z.object({
677
+ action: z.enum(["add", "toggle", "remove", "clear"]),
678
+ title: z.string().optional(),
679
+ id: z.string().optional(),
680
+ });
681
+
682
+ export type ManageTasksArgs = z.infer<typeof manageTasksParameters>;
683
+
684
+ export type NoteState = { title: string; content: string; color: string };
685
+
686
+ export const noteSchema = z.object({
687
+ title: z.string(),
688
+ content: z.string(),
689
+ color: z.enum(["yellow", "blue", "green", "pink"]),
690
+ });
691
+
692
+ export const noteInitialState: NoteState = {
693
+ title: "New Note",
694
+ content: "",
695
+ color: "yellow",
696
+ };
697
+
698
+ export const manageNotesParameters = z.object({
699
+ action: z.enum(["add", "remove", "clear"]),
700
+ noteId: z.string().optional(),
701
+ });
702
+
703
+ export type ManageNotesArgs = z.infer<typeof manageNotesParameters>;
704
+
705
+ ```
706
+
707
+ ## app/toolkits.ts
708
+
709
+ ```typescript
710
+ "use generative";
711
+
712
+ import { defineToolkit, stubTool } from "@assistant-ui/react";
713
+ import { manageNotesParameters, manageTasksParameters } from "./state";
714
+
715
+ export default defineToolkit({
716
+ manage_tasks: {
717
+ description:
718
+ 'Manage tasks on the task board. Actions: "add" (requires title), "toggle" (requires id), "remove" (requires id), "clear" (no extra fields).',
719
+ parameters: manageTasksParameters,
720
+ // The generative compiler strips this stub; TaskBoardToolOverrides provides the real executor.
721
+ execute: stubTool(),
722
+ renderText: {
723
+ running: ({ args }) => `Updating tasks: ${args.action}`,
724
+ complete: "Tasks updated",
725
+ },
726
+ },
727
+ manage_notes: {
728
+ description:
729
+ '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.',
730
+ parameters: manageNotesParameters,
731
+ // The generative compiler strips this stub; NotesToolOverrides provides the real executor.
732
+ execute: stubTool(),
733
+ renderText: {
734
+ running: ({ args }) => `Updating notes: ${args.action}`,
735
+ complete: "Notes updated",
736
+ },
737
+ },
738
+ });
739
+
740
+ ```
741
+
666
742
  ## components.json
667
743
 
668
744
  ```json
@@ -696,13 +772,14 @@ export default function Home() {
696
772
  ## next.config.ts
697
773
 
698
774
  ```typescript
775
+ import { withAui } from "@assistant-ui/next";
699
776
  import type { NextConfig } from "next";
700
777
 
701
778
  const nextConfig: NextConfig = {
702
779
  /* config options here */
703
780
  };
704
781
 
705
- export default nextConfig;
782
+ export default withAui(nextConfig);
706
783
 
707
784
  ```
708
785
 
@@ -720,14 +797,14 @@ export default nextConfig;
720
797
  "start": "next start"
721
798
  },
722
799
  "dependencies": {
723
- "@ai-sdk/openai": "^3.0.64",
800
+ "@ai-sdk/openai": "^3.0.67",
724
801
  "@assistant-ui/react": "workspace:*",
725
802
  "@assistant-ui/react-ai-sdk": "workspace:*",
726
803
  "@assistant-ui/ui": "workspace:*",
727
- "ai": "^6.0.188",
804
+ "ai": "^6.0.193",
728
805
  "class-variance-authority": "^0.7.1",
729
806
  "clsx": "^2.1.1",
730
- "lucide-react": "^1.16.0",
807
+ "lucide-react": "^1.17.0",
731
808
  "next": "^16.2.6",
732
809
  "react": "^19.2.6",
733
810
  "react-dom": "^19.2.6",
@@ -735,6 +812,7 @@ export default nextConfig;
735
812
  "zod": "^4.4.3"
736
813
  },
737
814
  "devDependencies": {
815
+ "@assistant-ui/next": "workspace:*",
738
816
  "@assistant-ui/x-buildutils": "workspace:*",
739
817
  "@tailwindcss/postcss": "^4.3.0",
740
818
  "@types/node": "^25.9.1",
@@ -760,7 +838,7 @@ Demonstrates **interactable components** — persistent UI components whose stat
760
838
 
761
839
  ### Task Board (single instance + custom tool)
762
840
  - `useInteractable("taskBoard", config)` — registers a single interactable
763
- - `useAssistantTool("manage_tasks")` — custom tool for incremental add/toggle/remove/clear
841
+ - `Tools({ toolkit })` — custom tool for incremental add/toggle/remove/clear
764
842
  - Auto-generated `update_taskBoard` tool with **partial updates** (AI only sends changed fields)
765
843
 
766
844
  ### Sticky Notes (multi-instance + selection + partial updates)
@@ -792,7 +870,7 @@ Open [http://localhost:3000](http://localhost:3000) to see the example.
792
870
  - **Partial updates** — auto-generated tools use partial schemas; AI only sends changed fields
793
871
  - **Multi-instance** — same `name`, different `id`; tools named `update_{name}_{id}`
794
872
  - **Selection** — `setSelected(true)` marks a component as focused for the AI
795
- - **`useAssistantTool`** — custom frontend tools for fine-grained control
873
+ - **`Tools({ toolkit })`** — custom frontend tools for fine-grained control
796
874
  - **`sendAutomaticallyWhen`** — auto-sends follow-up messages when tool calls complete
797
875
 
798
876
  ```