@assistant-ui/mcp-docs-server 0.1.4 → 0.1.5

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.
@@ -0,0 +1,129 @@
1
+ ---
2
+ title: AI SDK v5 (@alpha)
3
+ ---
4
+
5
+ import { Callout } from "fumadocs-ui/components/callout";
6
+
7
+ <Callout type="warn">
8
+ This integration is currently in **alpha**. APIs may change before the stable release.
9
+ </Callout>
10
+
11
+ ## Overview
12
+
13
+ Integration with the Vercel AI SDK v5's `useChat` hook using the `@assistant-ui/react-ai-sdk` package with the `@alpha` tag.
14
+ This version supports the latest AI SDK v5 features including the new streamText API and improved TypeScript support.
15
+
16
+ ## Getting Started
17
+
18
+ import { Steps, Step } from "fumadocs-ui/components/steps";
19
+
20
+ <Steps>
21
+ <Step>
22
+ ### Create a Next.JS project
23
+
24
+ ```sh
25
+ npx create-next-app@latest my-app
26
+ cd my-app
27
+ ```
28
+
29
+ </Step>
30
+ <Step>
31
+
32
+ ### Install AI SDK v5 and `@assistant-ui/react` with alpha tag
33
+
34
+ ```sh npm2yarn
35
+ npm install @assistant-ui/react @assistant-ui/react-ai-sdk@alpha ai @ai-sdk/openai
36
+ ```
37
+
38
+ </Step>
39
+ <Step>
40
+
41
+ ### Setup a backend route under `/api/chat`
42
+
43
+ `@/app/api/chat/route.ts`
44
+
45
+ ```tsx
46
+ import { openai } from "@ai-sdk/openai";
47
+ import {
48
+ streamText,
49
+ UIMessage,
50
+ convertToModelMessages,
51
+ tool,
52
+ } from "ai";
53
+ import { z } from "zod";
54
+
55
+ // Allow streaming responses up to 30 seconds
56
+ export const maxDuration = 30;
57
+
58
+ export async function POST(req: Request) {
59
+ const { messages }: { messages: UIMessage[] } = await req.json();
60
+
61
+ const result = streamText({
62
+ model: openai("gpt-4o"),
63
+ messages: convertToModelMessages(messages),
64
+ tools: {
65
+ get_current_weather: tool({
66
+ description: "Get the current weather",
67
+ inputSchema: z.object({
68
+ city: z.string(),
69
+ }),
70
+ execute: async ({ city }) => {
71
+ return `The weather in ${city} is sunny`;
72
+ },
73
+ }),
74
+ },
75
+ });
76
+
77
+ return result.toUIMessageStreamResponse();
78
+ }
79
+ ```
80
+
81
+ </Step>
82
+ <Step>
83
+
84
+ ### Wrap your app with `AssistantRuntimeProvider` with AI SDK v5 runtime
85
+
86
+ `@/app/page.tsx`
87
+
88
+ ```tsx
89
+ "use client";
90
+
91
+ import { Thread } from "@/components/assistant-ui/thread";
92
+ import { useChat } from "@ai-sdk/react";
93
+ import { AssistantRuntimeProvider } from "@assistant-ui/react";
94
+ import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
95
+
96
+ export default function Home() {
97
+ const chat = useChat();
98
+ const runtime = useAISDKRuntime(chat);
99
+
100
+ return (
101
+ <AssistantRuntimeProvider runtime={runtime}>
102
+ <div className="h-full">
103
+ <Thread />
104
+ </div>
105
+ </AssistantRuntimeProvider>
106
+ );
107
+ }
108
+ ```
109
+
110
+ </Step>
111
+ </Steps>
112
+
113
+ ## API Reference
114
+
115
+ ### useAISDKRuntime
116
+
117
+ Creates a runtime adapter for AI SDK v5's `useChat` hook.
118
+
119
+ ```tsx
120
+ import { useChat } from "@ai-sdk/react";
121
+ import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
122
+
123
+ const chat = useChat();
124
+ const runtime = useAISDKRuntime(chat);
125
+ ```
126
+
127
+ ## Example
128
+
129
+ For a complete example, check out the [AI SDK v5 example](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-ai-sdk-v5) in our repository.
@@ -38,7 +38,7 @@ npm install @assistant-ui/react @assistant-ui/react-ai-sdk ai @ai-sdk/openai
38
38
 
39
39
  ```tsx
40
40
  import { openai } from "@ai-sdk/openai";
41
- import { streamText } from "ai";
41
+ import { convertToCoreMessages, streamText } from "ai";
42
42
 
43
43
  export const maxDuration = 30;
44
44
 
@@ -0,0 +1,540 @@
1
+ ---
2
+ title: Message Part Grouping
3
+ ---
4
+
5
+ import { Steps, Step } from "fumadocs-ui/components/steps";
6
+ import { Callout } from "fumadocs-ui/components/callout";
7
+ import { ParametersTable } from "@/components/docs";
8
+
9
+ ## Overview
10
+
11
+ <Callout type="warn">
12
+ This feature is experimental and the API may change in future versions.
13
+ </Callout>
14
+
15
+ The Message Part Grouping feature allows you to organize and display message parts in custom groups using a flexible grouping function. This is useful for creating visual hierarchies, organizing related content together, or building custom UI patterns based on message part characteristics.
16
+
17
+ ## Basic Usage
18
+
19
+ Use the `MessagePrimitive.Unstable_PartsGrouped` component with a custom grouping function:
20
+
21
+ ```tsx twoslash title="/components/assistant-ui/thread.tsx"
22
+ import { FC, PropsWithChildren } from "react";
23
+ import { MessagePrimitive } from "@assistant-ui/react";
24
+
25
+ const AssistantActionBar: FC = () => null;
26
+ const BranchPicker: FC<{ className?: string }> = () => null;
27
+
28
+ // ---cut---
29
+ const AssistantMessage: FC = () => {
30
+ return (
31
+ <MessagePrimitive.Root className="...">
32
+ <div className="...">
33
+ <MessagePrimitive.Unstable_PartsGrouped
34
+ groupingFunction={(parts) => {
35
+ // Your custom grouping logic here
36
+ return [{ groupKey: undefined, indices: [0, 1, 2] }];
37
+ }}
38
+ components={{
39
+ Group: ({ groupKey, indices, children }) => {
40
+ // Your custom group rendering
41
+ return <div className="group">{children}</div>;
42
+ },
43
+ }}
44
+ />
45
+ </div>
46
+ <AssistantActionBar />
47
+ <BranchPicker className="..." />
48
+ </MessagePrimitive.Root>
49
+ );
50
+ };
51
+ ```
52
+
53
+ ## How Grouping Works
54
+
55
+ The grouping function receives all message parts and returns an array of groups. Each group contains:
56
+
57
+ - `groupKey`: A string identifier for the group (or `undefined` for ungrouped parts)
58
+ - `indices`: An array of indices indicating which parts belong to this group
59
+
60
+ ## Use Cases & Examples
61
+
62
+ ### Group by Parent ID
63
+
64
+ Group related content together using a parent-child relationship:
65
+
66
+ ```tsx
67
+ import { FC, PropsWithChildren, useState } from "react";
68
+ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
69
+
70
+ const groupByParentId = (parts: readonly any[]) => {
71
+ const groups = new Map<string, number[]>();
72
+
73
+ for (let i = 0; i < parts.length; i++) {
74
+ const part = parts[i];
75
+ const groupId = part?.parentId ?? `__ungrouped_${i}`;
76
+
77
+ const indices = groups.get(groupId) ?? [];
78
+ indices.push(i);
79
+ groups.set(groupId, indices);
80
+ }
81
+
82
+ return Array.from(groups.entries()).map(([groupId, indices]) => ({
83
+ groupKey: groupId.startsWith("__ungrouped_") ? undefined : groupId,
84
+ indices,
85
+ }));
86
+ };
87
+
88
+ // Usage with collapsible groups
89
+ const CollapsibleGroup: FC<
90
+ PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
91
+ > = ({ groupKey, indices, children }) => {
92
+ const [isCollapsed, setIsCollapsed] = useState(false);
93
+
94
+ if (!groupKey) return <>{children}</>;
95
+
96
+ return (
97
+ <div className="my-2 overflow-hidden rounded-lg border">
98
+ <button
99
+ onClick={() => setIsCollapsed(!isCollapsed)}
100
+ className="hover:bg-muted/50 flex w-full items-center justify-between p-3"
101
+ >
102
+ <span>
103
+ Group {groupKey} ({indices.length} items)
104
+ </span>
105
+ {isCollapsed ? <ChevronDownIcon /> : <ChevronUpIcon />}
106
+ </button>
107
+ {!isCollapsed && <div className="border-t p-3">{children}</div>}
108
+ </div>
109
+ );
110
+ };
111
+ ```
112
+
113
+ ### Group by Tool Name
114
+
115
+ Organize tool calls by their type:
116
+
117
+ ```tsx
118
+ import { FC, PropsWithChildren } from "react";
119
+
120
+ const groupByToolName = (parts: readonly any[]) => {
121
+ const groups = new Map<string, number[]>();
122
+
123
+ for (let i = 0; i < parts.length; i++) {
124
+ const part = parts[i];
125
+ // Group tool calls by tool name, everything else ungrouped
126
+ const groupKey = part.type === "tool-call" ? part.toolName : `__other_${i}`;
127
+
128
+ const indices = groups.get(groupKey) ?? [];
129
+ indices.push(i);
130
+ groups.set(groupKey, indices);
131
+ }
132
+
133
+ return Array.from(groups.entries()).map(([groupKey, indices]) => ({
134
+ groupKey: groupKey.startsWith("__other_") ? undefined : groupKey,
135
+ indices,
136
+ }));
137
+ };
138
+
139
+ // Render tool groups with custom styling
140
+ const ToolGroup: FC<
141
+ PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
142
+ > = ({ groupKey, indices, children }) => {
143
+ if (!groupKey) return <>{children}</>;
144
+
145
+ return (
146
+ <div className="tool-group my-2 rounded-lg border">
147
+ <div className="bg-muted/50 px-4 py-2 text-sm font-medium">
148
+ Tool: {groupKey} ({indices.length} calls)
149
+ </div>
150
+ <div className="p-4">{children}</div>
151
+ </div>
152
+ );
153
+ };
154
+ ```
155
+
156
+ ### Group Consecutive Text Parts
157
+
158
+ Combine multiple text parts into cohesive blocks:
159
+
160
+ ```tsx
161
+ import { FC, PropsWithChildren } from "react";
162
+
163
+ type MessagePartGroup = {
164
+ groupKey: string | undefined;
165
+ indices: number[];
166
+ };
167
+
168
+ const groupConsecutiveText = (parts: readonly any[]) => {
169
+ const groups: MessagePartGroup[] = [];
170
+ let currentGroup: number[] = [];
171
+ let isTextGroup = false;
172
+
173
+ for (let i = 0; i < parts.length; i++) {
174
+ const isText = parts[i].type === "text";
175
+
176
+ if (isText === isTextGroup && currentGroup.length > 0) {
177
+ currentGroup.push(i);
178
+ } else {
179
+ if (currentGroup.length > 0) {
180
+ groups.push({
181
+ groupKey: isTextGroup ? "text-block" : undefined,
182
+ indices: currentGroup,
183
+ });
184
+ }
185
+ currentGroup = [i];
186
+ isTextGroup = isText;
187
+ }
188
+ }
189
+
190
+ if (currentGroup.length > 0) {
191
+ groups.push({
192
+ groupKey: isTextGroup ? "text-block" : undefined,
193
+ indices: currentGroup,
194
+ });
195
+ }
196
+
197
+ return groups;
198
+ };
199
+
200
+ // Render text blocks with special formatting
201
+ const TextBlockGroup: FC<
202
+ PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
203
+ > = ({ groupKey, indices, children }) => {
204
+ if (groupKey === "text-block") {
205
+ return (
206
+ <div className="prose prose-sm my-2 rounded-lg bg-gray-50 p-4">
207
+ {children}
208
+ </div>
209
+ );
210
+ }
211
+ return <>{children}</>;
212
+ };
213
+ ```
214
+
215
+ ### Group by Content Type
216
+
217
+ Separate different types of content for distinct visual treatment:
218
+
219
+ ```tsx
220
+ type MessagePartGroup = {
221
+ groupKey: string | undefined;
222
+ indices: number[];
223
+ };
224
+
225
+ const groupByContentType = (parts: readonly any[]) => {
226
+ const typeGroups = new Map<string, number[]>();
227
+
228
+ for (let i = 0; i < parts.length; i++) {
229
+ const part = parts[i];
230
+ const type = part.type;
231
+
232
+ const indices = typeGroups.get(type) ?? [];
233
+ indices.push(i);
234
+ typeGroups.set(type, indices);
235
+ }
236
+
237
+ // Order groups by type priority
238
+ const typeOrder = [
239
+ "reasoning",
240
+ "tool-call",
241
+ "source",
242
+ "text",
243
+ "image",
244
+ "file",
245
+ ];
246
+ const orderedGroups: MessagePartGroup[] = [];
247
+
248
+ for (const type of typeOrder) {
249
+ const indices = typeGroups.get(type);
250
+ if (indices && indices.length > 0) {
251
+ orderedGroups.push({ groupKey: type, indices });
252
+ }
253
+ }
254
+
255
+ // Add any remaining types
256
+ for (const [type, indices] of typeGroups) {
257
+ if (!typeOrder.includes(type)) {
258
+ orderedGroups.push({ groupKey: type, indices });
259
+ }
260
+ }
261
+
262
+ return orderedGroups;
263
+ };
264
+ ```
265
+
266
+ ### Group by Custom Metadata
267
+
268
+ Use any custom metadata in your parts for grouping:
269
+
270
+ ```tsx
271
+ import { FC, PropsWithChildren } from "react";
272
+
273
+ type MessagePartGroup = {
274
+ groupKey: string | undefined;
275
+ indices: number[];
276
+ };
277
+
278
+ const groupByPriority = (parts: readonly any[]) => {
279
+ const priorityGroups = new Map<string, number[]>();
280
+
281
+ for (let i = 0; i < parts.length; i++) {
282
+ const part = parts[i];
283
+ // Assume parts have a custom priority field
284
+ const priority = part.priority || "normal";
285
+
286
+ const indices = priorityGroups.get(priority) ?? [];
287
+ indices.push(i);
288
+ priorityGroups.set(priority, indices);
289
+ }
290
+
291
+ // Order by priority
292
+ const priorityOrder = ["high", "normal", "low"];
293
+ const orderedGroups: MessagePartGroup[] = [];
294
+
295
+ for (const priority of priorityOrder) {
296
+ const indices = priorityGroups.get(priority);
297
+ if (indices) {
298
+ orderedGroups.push({ groupKey: priority, indices });
299
+ }
300
+ }
301
+
302
+ return orderedGroups;
303
+ };
304
+
305
+ // Render with priority indicators
306
+ const PriorityGroup: FC<
307
+ PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
308
+ > = ({ groupKey, indices, children }) => {
309
+ if (!groupKey) return <>{children}</>;
310
+
311
+ const priorityStyles = {
312
+ high: "border-red-500 bg-red-50",
313
+ normal: "border-gray-300 bg-white",
314
+ low: "border-gray-200 bg-gray-50",
315
+ };
316
+
317
+ return (
318
+ <div
319
+ className={`my-2 rounded-lg border-2 p-4 ${priorityStyles[groupKey] || ""}`}
320
+ >
321
+ <div className="mb-2 text-xs font-semibold uppercase text-gray-600">
322
+ {groupKey} Priority
323
+ </div>
324
+ {children}
325
+ </div>
326
+ );
327
+ };
328
+ ```
329
+
330
+ ## Integration with Assistant Streams
331
+
332
+ When using assistant-stream libraries, you can add custom metadata to parts:
333
+
334
+ ### Python (assistant-stream)
335
+
336
+ ```python
337
+ from assistant_stream import create_run
338
+
339
+ async def my_run(controller):
340
+ # Add parts with custom parentId
341
+ research_controller = controller.with_parent_id("research-123")
342
+
343
+ await research_controller.add_tool_call("search", {"query": "climate data"})
344
+ research_controller.append_text("Key findings from the research:")
345
+
346
+ # Add parts with custom metadata
347
+ controller.append_part({
348
+ "type": "text",
349
+ "text": "High priority finding",
350
+ "priority": "high",
351
+ "category": "findings"
352
+ })
353
+ ```
354
+
355
+ ### TypeScript (assistant-stream)
356
+
357
+ ```typescript
358
+ import { createAssistantStream } from "@assistant-ui/react/assistant-stream";
359
+
360
+ const stream = createAssistantStream(async (controller) => {
361
+ // Add parts with parentId
362
+ const researchController = controller.withParentId("research-123");
363
+
364
+ await researchController.addToolCallPart({
365
+ toolName: "search",
366
+ args: { query: "climate data" },
367
+ });
368
+
369
+ // Add parts with custom metadata
370
+ controller.appendPart({
371
+ type: "text",
372
+ text: "High priority finding",
373
+ priority: "high",
374
+ category: "findings",
375
+ });
376
+ });
377
+ ```
378
+
379
+ ## API Reference
380
+
381
+ ### MessagePrimitive.Unstable_PartsGrouped
382
+
383
+ <ParametersTable
384
+ type="MessagePrimitiveUnstable_PartsGroupedProps"
385
+ parameters={[
386
+ {
387
+ name: "groupingFunction",
388
+ type: "(parts: readonly any[]) => MessagePartGroup[]",
389
+ description:
390
+ "Function that takes an array of message parts and returns an array of groups. Each group contains a groupKey (for identification) and an array of indices.",
391
+ required: true,
392
+ },
393
+ {
394
+ name: "components",
395
+ type: "object",
396
+ description:
397
+ "Component configuration for rendering different types of message content and groups.",
398
+ children: [
399
+ {
400
+ type: "Components",
401
+ parameters: [
402
+ {
403
+ name: "Empty",
404
+ type: "EmptyMessagePartComponent",
405
+ description: "Component for rendering empty messages",
406
+ },
407
+ {
408
+ name: "Text",
409
+ type: "TextMessagePartComponent",
410
+ description: "Component for rendering text content",
411
+ },
412
+ {
413
+ name: "Reasoning",
414
+ type: "ReasoningMessagePartComponent",
415
+ description:
416
+ "Component for rendering reasoning content (typically hidden)",
417
+ },
418
+ {
419
+ name: "Source",
420
+ type: "SourceMessagePartComponent",
421
+ description: "Component for rendering source content",
422
+ },
423
+ {
424
+ name: "Image",
425
+ type: "ImageMessagePartComponent",
426
+ description: "Component for rendering image content",
427
+ },
428
+ {
429
+ name: "File",
430
+ type: "FileMessagePartComponent",
431
+ description: "Component for rendering file content",
432
+ },
433
+ {
434
+ name: "Unstable_Audio",
435
+ type: "Unstable_AudioMessagePartComponent",
436
+ description:
437
+ "Component for rendering audio content (experimental)",
438
+ },
439
+ {
440
+ name: "tools",
441
+ type: "object | { Override: ComponentType }",
442
+ description:
443
+ "Configuration for tool call rendering. Can be an object with by_name map and Fallback component, or an Override component.",
444
+ },
445
+ {
446
+ name: "Group",
447
+ type: "ComponentType<PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>>",
448
+ description:
449
+ "Component for rendering grouped message parts. Receives groupKey, indices array, and children to render.",
450
+ },
451
+ ],
452
+ },
453
+ ],
454
+ },
455
+ ]}
456
+ />
457
+
458
+ ### MessagePartGroup Type
459
+
460
+ ```typescript
461
+ type MessagePartGroup = {
462
+ groupKey: string | undefined; // The group identifier (undefined for ungrouped parts)
463
+ indices: number[]; // Array of part indices belonging to this group
464
+ };
465
+ ```
466
+
467
+ ### Group Component Props
468
+
469
+ The Group component receives:
470
+
471
+ - `groupKey`: The group identifier (or `undefined` for ungrouped parts)
472
+ - `indices`: Array of indices for the parts in this group
473
+ - `children`: The rendered message part components
474
+
475
+ ## Best Practices
476
+
477
+ 1. **Keep grouping logic simple**: Complex grouping functions can impact performance
478
+ 2. **Handle ungrouped parts**: Always consider parts that don't match any group criteria
479
+ 3. **Maintain order**: Consider the visual flow when ordering groups
480
+ 4. **Use meaningful keys**: Group keys should be descriptive for debugging
481
+ 5. **Test edge cases**: Empty messages, single parts, and large numbers of parts
482
+
483
+ ## Common Patterns
484
+
485
+ ### Conditional Grouping
486
+
487
+ Only group when certain conditions are met:
488
+
489
+ ```tsx
490
+ const conditionalGrouping = (parts: readonly any[]) => {
491
+ // Only group if there are enough parts
492
+ if (parts.length < 5) {
493
+ return [{ groupKey: undefined, indices: parts.map((_, i) => i) }];
494
+ }
495
+
496
+ // Your grouping logic here
497
+ };
498
+ ```
499
+
500
+ ### Nested Grouping
501
+
502
+ Create hierarchical groups:
503
+
504
+ ```tsx
505
+ const nestedGrouping = (parts: readonly any[]) => {
506
+ // First level: group by type
507
+ // Second level: group by subtype or metadata
508
+ // Implementation depends on your specific needs
509
+ };
510
+ ```
511
+
512
+ ### Dynamic Group Rendering
513
+
514
+ Adjust group appearance based on content:
515
+
516
+ ```tsx
517
+ import { FC, PropsWithChildren } from "react";
518
+ import { useMessage } from "@assistant-ui/react";
519
+
520
+ const DynamicGroup: FC<
521
+ PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
522
+ > = ({ groupKey, indices, children }) => {
523
+ const parts = useMessage((m) => m.content);
524
+ const groupParts = indices.map((i) => parts[i]);
525
+
526
+ // Analyze group content
527
+ const hasErrors = groupParts.some((p) => p.error);
528
+ const isLoading = groupParts.some((p) => p.status?.type === "running");
529
+
530
+ if (!groupKey) return <>{children}</>;
531
+
532
+ return (
533
+ <div
534
+ className={`my-2 rounded-lg border p-4 ${hasErrors ? "border-red-500 bg-red-50" : ""} ${isLoading ? "animate-pulse" : ""} `}
535
+ >
536
+ {children}
537
+ </div>
538
+ );
539
+ };
540
+ ```