Add v1 reference selector and content

This commit is contained in:
Tyler Slaton
2026-05-28 13:22:11 -07:00
parent 9756697854
commit ec239b15f7
29 changed files with 2611 additions and 189 deletions
@@ -1,8 +1,10 @@
import type { Metadata } from "next";
import type React from "react";
import Link from "next/link";
import { notFound } from "next/navigation";
import { MDXRemote } from "next-mdx-remote/rsc";
import matter from "gray-matter";
import { LinkIcon } from "lucide-react";
import { PropertyReference } from "@/components/property-reference";
import {
Callout,
@@ -19,14 +21,16 @@ import {
DocsDescription,
} from "fumadocs-ui/page";
import { ShellDocsLayout } from "@/components/shell-docs-layout";
import type * as PageTree from "fumadocs-core/page-tree";
import { ReferenceVersionSelector } from "@/components/reference-version-selector";
import {
REFERENCE_CONTENT_DIR,
loadAllReferenceItems,
REFERENCE_VERSIONS,
buildReferencePageTree,
referenceHref,
referenceStaticParams,
referenceVersionHref,
resolveReferencePage,
} from "@/lib/reference-items";
import { stripLeadingImports } from "@/lib/docs-render";
import { safeReadFileSync } from "@/lib/safe-fs";
import { buildDocMetadata } from "@/lib/seo-metadata";
// Self-canonical for /reference/<slug>. Reference pages are not
@@ -41,11 +45,8 @@ export async function generateMetadata({
params: Promise<{ slug: string[] }>;
}): Promise<Metadata> {
const { slug } = await params;
const slugPath = slug.join("/");
const canonicalPath = `/reference/${slugPath}`;
// Read the reference MDX directly to extract frontmatter. Reuse
// safeReadFileSync so a crafted slug can't escape REFERENCE_CONTENT_DIR.
const raw = safeReadFileSync(REFERENCE_CONTENT_DIR, `${slugPath}.mdx`);
const resolved = resolveReferencePage(slug);
const raw = resolved?.raw ?? null;
let title: string | undefined;
let description: string | undefined;
if (raw !== null) {
@@ -64,7 +65,9 @@ export async function generateMetadata({
return buildDocMetadata({
title: title ?? slug[slug.length - 1],
description,
canonicalPath,
canonicalPath: resolved
? referenceHref(resolved.version, resolved.pageSlug)
: `/reference/${slug.join("/")}`,
});
}
@@ -77,6 +80,12 @@ const mdxComponents = {
Accordions,
Accordion,
OpsPlatformCTA,
LinkIcon,
Frame: ({ children }: { children: React.ReactNode }) => (
<div className="my-6 rounded-lg border border-[var(--border)] bg-[var(--bg-surface)] p-4">
{children}
</div>
),
// Strip unknown imports — MDX import statements become no-ops in next-mdx-remote
};
@@ -90,15 +99,12 @@ export default async function ReferenceSlugPage({
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
const slugPath = slug.join("/");
// slugPath is user-supplied (URL segments). Route the filesystem read
// through safeReadFileSync so crafted paths like `..%2F..%2Fsecrets`
// can't escape REFERENCE_CONTENT_DIR.
const raw = safeReadFileSync(REFERENCE_CONTENT_DIR, `${slugPath}.mdx`);
if (raw === null) {
const resolved = resolveReferencePage(slug);
if (resolved === null) {
notFound();
}
const { version, pageSlug, contentSlug, raw } = resolved;
let content = "";
let data: Record<string, unknown> = {};
try {
@@ -107,7 +113,7 @@ export default async function ReferenceSlugPage({
data = parsed.data;
} catch (err) {
console.error(
`[reference] Failed to parse frontmatter in ${slugPath}.mdx:`,
`[reference] Failed to parse frontmatter in ${contentSlug}.mdx:`,
err,
);
notFound();
@@ -115,35 +121,28 @@ export default async function ReferenceSlugPage({
const cleanedContent = stripLeadingImports(content);
const allItems = loadAllReferenceItems();
const title =
typeof data.title === "string" && data.title.length > 0
? data.title
: slug[slug.length - 1];
const description =
typeof data.description === "string" ? data.description : undefined;
// Build a Fumadocs PageTree from the reference items, grouped by
// category. Reference's IA is its own (Components / Hooks) — we don't
// share the docs nav tree here.
const pageTree: PageTree.Root = {
name: "Reference",
children: ["Components", "Hooks"].flatMap((cat) => [
{ type: "separator" as const, name: cat },
...allItems
.filter((i) => i.category === cat)
.map(
(item): PageTree.Item => ({
type: "page",
name: item.title,
url: `/reference/${item.slug}`,
}),
),
]),
};
const pageTree = buildReferencePageTree(version);
const versionOptions = REFERENCE_VERSIONS.map((referenceVersion) => ({
version: referenceVersion,
href: referenceVersionHref(referenceVersion, pageSlug),
}));
return (
<ShellDocsLayout tree={pageTree}>
<ShellDocsLayout
tree={pageTree}
banner={
<ReferenceVersionSelector
activeVersion={version}
options={versionOptions}
/>
}
>
<DocsPage
toc={[]}
tableOfContent={{ enabled: false }}
@@ -161,7 +160,15 @@ export default async function ReferenceSlugPage({
Reference
</Link>
{" / "}
<span className="capitalize">{slug[0]}</span>
<span>{version}</span>
{pageSlug && (
<>
{" / "}
<span className="capitalize">
{pageSlug.split("/")[0]}
</span>
</>
)}
</div>
<DocsTitle className="text-2xl font-bold">{title}</DocsTitle>
{description && (
+67 -96
View File
@@ -1,60 +1,50 @@
import Link from "next/link";
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { DocsPage } from "fumadocs-ui/page";
import { ShellDocsLayout } from "@/components/shell-docs-layout";
import type * as PageTree from "fumadocs-core/page-tree";
import { ReferenceVersionSelector } from "@/components/reference-version-selector";
import {
REFERENCE_CONTENT_DIR,
loadReferenceItems,
loadAllReferenceItems,
REFERENCE_CATEGORIES,
REFERENCE_VERSIONS,
buildReferencePageTree,
loadReferenceVersionItems,
referenceVersionHref,
readReferenceIndexDescription,
} from "@/lib/reference-items";
import type { ReferenceCategory, ReferenceItem } from "@/lib/reference-items";
function displayTitle(item: ReferenceItem): string {
if (item.category === "Components") return `<${item.title} />`;
if (item.category === "Hooks") return `${item.title}()`;
return item.title;
}
function categoryItems(
items: ReferenceItem[],
category: ReferenceCategory,
): ReferenceItem[] {
return items.filter((item) => item.category === category);
}
export default function ReferencePage() {
const components = loadReferenceItems("components");
const hooks = loadReferenceItems("hooks");
const allItems = loadAllReferenceItems();
// Mirror the PageTree built by `/reference/[...slug]` so the sidebar
// chrome is identical between the index and the per-item pages.
const pageTree: PageTree.Root = {
name: "Reference",
children: ["Components", "Hooks"].flatMap((cat) => [
{ type: "separator" as const, name: cat },
...allItems
.filter((i) => i.category === cat)
.map(
(item): PageTree.Item => ({
type: "page",
name: item.title,
url: `/reference/${item.slug}`,
}),
),
]),
};
// Also load the index page frontmatter for the intro. Guarded so a
// malformed frontmatter block falls back to a default rather than
// crashing the whole index page.
let intro = "API Reference for the next-generation CopilotKit React API.";
const indexPath = path.join(REFERENCE_CONTENT_DIR, "index.mdx");
if (fs.existsSync(indexPath)) {
try {
const { data } = matter(fs.readFileSync(indexPath, "utf-8"));
if (typeof data.description === "string" && data.description.length > 0) {
intro = data.description;
}
} catch (err) {
console.error(
`[reference] Failed to parse frontmatter in ${indexPath}:`,
err,
);
}
}
const activeVersion = "v2";
const allItems = loadReferenceVersionItems(activeVersion);
const pageTree = buildReferencePageTree(activeVersion);
const intro = readReferenceIndexDescription(activeVersion);
const versionOptions = REFERENCE_VERSIONS.map((version) => ({
version,
href: referenceVersionHref(version),
}));
return (
<ShellDocsLayout tree={pageTree}>
<ShellDocsLayout
tree={pageTree}
banner={
<ReferenceVersionSelector
activeVersion={activeVersion}
options={versionOptions}
/>
}
>
<DocsPage
toc={[]}
tableOfContent={{ enabled: false }}
@@ -68,55 +58,36 @@ export default function ReferencePage() {
</h1>
<p className="text-[var(--text-muted)] text-sm mb-10">{intro}</p>
<section className="mb-10">
<h2 className="text-lg font-semibold text-[var(--text)] mb-4">
UI Components
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{components.map((item) => (
<Link
key={item.slug}
href={`/reference/${item.slug}`}
className="block rounded-lg border border-[var(--border)] bg-[var(--bg-surface)] p-4 hover:bg-[var(--bg-elevated)] transition-colors"
>
<div className="font-mono text-sm font-semibold text-[var(--accent)]">
{"<"}
{item.title}
{" />"}
</div>
{item.description && (
<div className="text-xs text-[var(--text-muted)] mt-1">
{item.description}
</div>
)}
</Link>
))}
</div>
</section>
{REFERENCE_CATEGORIES.map((category) => {
const items = categoryItems(allItems, category);
if (items.length === 0) return null;
<section>
<h2 className="text-lg font-semibold text-[var(--text)] mb-4">
Hooks
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{hooks.map((item) => (
<Link
key={item.slug}
href={`/reference/${item.slug}`}
className="block rounded-lg border border-[var(--border)] bg-[var(--bg-surface)] p-4 hover:bg-[var(--bg-elevated)] transition-colors"
>
<div className="font-mono text-sm font-semibold text-[var(--accent)]">
{item.title}()
</div>
{item.description && (
<div className="text-xs text-[var(--text-muted)] mt-1">
{item.description}
</div>
)}
</Link>
))}
</div>
</section>
return (
<section key={category} className="mb-10 last:mb-0">
<h2 className="text-lg font-semibold text-[var(--text)] mb-4">
{category === "Components" ? "UI Components" : category}
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{items.map((item) => (
<Link
key={item.slug}
href={item.url}
className="block rounded-lg border border-[var(--border)] bg-[var(--bg-surface)] p-4 hover:bg-[var(--bg-elevated)] transition-colors"
>
<div className="font-mono text-sm font-semibold text-[var(--accent)]">
{displayTitle(item)}
</div>
{item.description && (
<div className="text-xs text-[var(--text-muted)] mt-1">
{item.description}
</div>
)}
</Link>
))}
</div>
</section>
);
})}
</div>
</DocsPage>
</ShellDocsLayout>
@@ -0,0 +1,119 @@
"use client";
import Link from "next/link";
import { ChevronDown } from "lucide-react";
import { useEffect, useRef, useState } from "react";
export type ReferenceVersion = "v2" | "v1";
export type ReferenceVersionOption = {
version: ReferenceVersion;
href: string;
};
const VERSION_LABELS: Record<ReferenceVersion, string> = {
v2: "v2",
v1: "v1",
};
export function ReferenceVersionSelector({
activeVersion,
options,
}: {
activeVersion: ReferenceVersion;
options: ReferenceVersionOption[];
}) {
const [open, setOpen] = useState(false);
const panelRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (!open) return;
const handleClick = (event: MouseEvent) => {
const target = event.target instanceof Node ? event.target : null;
if (!target) return;
if (
panelRef.current?.contains(target) ||
buttonRef.current?.contains(target)
) {
return;
}
setOpen(false);
};
const handleKey = (event: KeyboardEvent) => {
if (event.key === "Escape") setOpen(false);
};
document.addEventListener("mousedown", handleClick);
document.addEventListener("keydown", handleKey);
return () => {
document.removeEventListener("mousedown", handleClick);
document.removeEventListener("keydown", handleKey);
};
}, [open]);
return (
<div className="sticky top-0 z-10 bg-[var(--bg-surface)] backdrop-blur-lg">
<div className="relative">
<button
ref={buttonRef}
type="button"
onClick={() => setOpen((value) => !value)}
aria-haspopup="listbox"
aria-expanded={open}
className="flex h-12 w-full cursor-pointer items-center gap-2 rounded-xl border border-[var(--accent)] bg-[var(--accent-light)] p-1.5 text-[13px] font-medium text-[var(--text)] transition-colors hover:border-[var(--accent)]"
>
<span
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-[var(--accent)]/25 text-base dark:bg-white/10"
aria-hidden="true"
>
🪁
</span>
<span className="min-w-0 flex-1 text-left">
<span className="block truncate leading-tight">
{VERSION_LABELS[activeVersion]}
</span>
<span className="mt-0.5 block text-[9px] uppercase leading-tight tracking-wider text-[var(--text-faint)]">
API version
</span>
</span>
<ChevronDown className="mr-0.5 h-3.5 w-3.5 shrink-0 text-[var(--text-muted)]" />
</button>
{open && (
<div
ref={panelRef}
role="listbox"
className="absolute left-0 right-0 top-full z-50 mt-1 rounded-lg border border-[var(--border)] bg-[var(--bg-surface)] p-2 shadow-lg"
>
{options.map(({ version, href }) => {
const active = version === activeVersion;
return (
<Link
key={version}
href={href}
aria-current={active ? "page" : undefined}
onClick={() => setOpen(false)}
className={[
"flex w-full items-center gap-2 rounded px-2 py-1.5 text-[13px] transition-colors",
active
? "bg-[var(--accent-light)] text-[var(--accent)]"
: "text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:text-[var(--text)]",
].join(" ")}
>
<span aria-hidden="true" className="shrink-0 text-sm">
🪁
</span>
<span className="min-w-0 flex-1 truncate">
{VERSION_LABELS[version]}
</span>
</Link>
);
})}
</div>
)}
</div>
</div>
);
}
@@ -0,0 +1,11 @@
{
"title": "LLM Adapters",
"pages": [
"OpenAIAdapter",
"OpenAIAssistantAdapter",
"AnthropicAdapter",
"LangChainAdapter",
"GroqAdapter",
"GoogleGenerativeAIAdapter"
]
}
@@ -0,0 +1,8 @@
{
"title": "Copilot Runtime",
"pages": [
"CopilotRuntime",
"llm-adapters",
"CopilotTask"
]
}
@@ -0,0 +1,29 @@
---
title: All Chat Components
---
import { LinkIcon } from "lucide-react"
<Callout type="warning">
v1 chat components are still supported, but we recommend using the [v2 chat components](/reference/v2/components/CopilotChat) for new projects.
</Callout>
<Cards>
<Card
title="CopilotChat"
description="The CopilotChat component, providing a chat interface for interacting with your copilot."
href="/reference/v1/components/chat/CopilotChat"
icon={<LinkIcon />}
/>
<Card
title="CopilotPopup"
description="The CopilotPopup component, providing a popup interface for interacting with your copilot."
href="/reference/v1/components/chat/CopilotPopup"
icon={<LinkIcon />}
/>
<Card
title="CopilotSidebar"
description="The CopilotSidebar component, providing a sidebar interface for interacting with your copilot."
href="/reference/v1/components/chat/CopilotSidebar"
icon={<LinkIcon />}
/>
</Cards>
@@ -0,0 +1,8 @@
{
"title": "Chat Components",
"pages": [
"CopilotChat",
"CopilotPopup",
"CopilotSidebar"
]
}
@@ -0,0 +1,8 @@
{
"title": "UI Components",
"pages": [
"chat",
"CopilotTextarea",
"CopilotKit"
]
}
@@ -0,0 +1,19 @@
{
"title": "hooks",
"pages": [
"useAgent",
"useDefaultTool",
"useFrontendTool",
"useRenderToolCall",
"useHumanInTheLoop",
"useCopilotReadable",
"useCopilotAdditionalInstructions",
"useCopilotChat",
"useCopilotChatHeadless_c",
"useCopilotChatSuggestions",
"useCoAgent",
"useCoAgentStateRender",
"useLangGraphInterrupt",
"useCopilotAction"
]
}
@@ -0,0 +1,304 @@
---
title: "useAgent"
description: "React hook for accessing AG-UI agent instances"
---
<Callout type="warning">
`useAgent` is still supported, but we recommend migrating to [`useAgent`](/reference/v2/hooks/useAgent) from the v2 API (`@copilotkit/react-core/v2`).
</Callout>
## Overview
`useAgent` is a React hook that returns an [AG-UI AbstractAgent](https://docs.ag-ui.com/sdk/js/client/abstract-agent) instance. The hook subscribes to agent state changes and triggers re-renders when the agent's state, messages, or execution status changes.
**Throws error** if no agent is configured with the specified `agentId`.
## Signature
```tsx
function useAgent(options?: UseAgentOptions): { agent: AbstractAgent }
```
## Parameters
<PropertyReference name="options" type="UseAgentOptions">
Configuration object for the hook.
<PropertyReference name="agentId" type="string" default='"default"'>
ID of the agent to retrieve. Must match an agent configured in `CopilotKitProvider`.
</PropertyReference>
<PropertyReference name="updates" type="UseAgentUpdate[]" default="[OnMessagesChanged, OnStateChanged, OnRunStatusChanged]">
Controls which agent changes trigger component re-renders. Options:
- `UseAgentUpdate.OnMessagesChanged` - Re-render when messages change
- `UseAgentUpdate.OnStateChanged` - Re-render when state changes
- `UseAgentUpdate.OnRunStatusChanged` - Re-render when execution status changes
Pass an empty array `[]` to prevent automatic re-renders.
</PropertyReference>
</PropertyReference>
## Return Value
<PropertyReference name="object" type="{ agent: AbstractAgent }">
Object containing the agent instance.
<PropertyReference name="agent" type="AbstractAgent">
The AG-UI agent instance. See [AbstractAgent documentation](https://docs.ag-ui.com/sdk/js/client/abstract-agent) for full interface details.
### Core Properties
<PropertyReference name="agentId" type="string | undefined">
Unique identifier for the agent instance.
</PropertyReference>
<PropertyReference name="description" type="string">
Human-readable description of the agent's purpose.
</PropertyReference>
<PropertyReference name="threadId" type="string">
Unique identifier for the current conversation thread.
</PropertyReference>
<PropertyReference name="messages" type="Message[]">
Array of conversation messages. Each message contains:
- `id: string` - Unique message identifier
- `role: "user" | "assistant" | "system"` - Message role
- `content: string` - Message content
</PropertyReference>
<PropertyReference name="state" type="any">
Shared state object synchronized between application and agent. Both can read and modify this state.
</PropertyReference>
<PropertyReference name="isRunning" type="boolean">
Indicates whether the agent is currently executing.
</PropertyReference>
<PropertyReference name="debug" type="boolean">
Enables debug logging for agent events and execution.
</PropertyReference>
### Methods
<PropertyReference name="runAgent" type="(options?: RunAgentOptions) => Promise<void>">
Manually triggers agent execution.
**Parameters:**
- `options.forwardedProps?: any` - Data to pass to the agent execution context
**Example:**
```tsx
await agent.runAgent({
forwardedProps: {
command: { resume: "user response" }
}
});
```
</PropertyReference>
<PropertyReference name="setState" type="(newState: any) => void">
Updates the shared state. Changes are immediately available to both application and agent.
**Example:**
```tsx
agent.setState({
...agent.state,
theme: "dark"
});
```
</PropertyReference>
<PropertyReference name="subscribe" type="(subscriber: AgentSubscriber) => { unsubscribe: () => void }">
Subscribes to agent events. Returns cleanup function.
**Subscriber Events:**
- `onCustomEvent?: ({ event: { name: string, value: any } }) => void` - Custom events (e.g., LangGraph interrupts)
- `onRunStartedEvent?: () => void` - Agent execution starts
- `onRunFinalized?: () => void` - Agent execution completes
- `onStateChanged?: (state: any) => void` - State changes
- `onMessagesChanged?: (messages: Message[]) => void` - Messages added/modified
**Example:**
```tsx
const { unsubscribe } = agent.subscribe({
onCustomEvent: ({ event }) => {
console.log(event.name, event.value);
}
});
// Cleanup
unsubscribe();
```
</PropertyReference>
<PropertyReference name="addMessage" type="(message: Message) => void">
Adds a single message to the conversation and notifies subscribers.
**Example:**
```tsx
agent.addMessage({
id: crypto.randomUUID(),
role: "user",
content: "Hello"
});
```
</PropertyReference>
<PropertyReference name="addMessages" type="(messages: Message[]) => void">
Adds multiple messages to the conversation and notifies subscribers once.
</PropertyReference>
<PropertyReference name="setMessages" type="(messages: Message[]) => void">
Replaces the entire message history with a new array of messages.
</PropertyReference>
<PropertyReference name="connectAgent" type="(options?: RunAgentParameters) => Promise<RunAgentResult>">
Connects to a streaming agent endpoint. Similar to `runAgent` but uses the `connect()` method for persistent connections.
</PropertyReference>
<PropertyReference name="detachActiveRun" type="() => Promise<void>">
Detaches from the currently active agent run without aborting it. The run continues in the background but stops updating the local agent state.
</PropertyReference>
<PropertyReference name="abortRun" type="() => void">
Aborts the currently running agent execution. Implementation varies by agent type.
</PropertyReference>
<PropertyReference name="clone" type="() => AbstractAgent">
Creates a deep copy of the agent with cloned messages, state, and configuration.
</PropertyReference>
<PropertyReference name="use" type="(...middlewares: Middleware[]) => this">
Adds middleware to the agent's execution pipeline. Middlewares can intercept and transform agent runs.
</PropertyReference>
</PropertyReference>
</PropertyReference>
## Usage
### Basic Usage
```tsx
import { useAgent } from "@copilotkit/react-core/v2";
function AgentStatus() {
const { agent } = useAgent();
return (
<div>
<div>Agent: {agent.agentId}</div>
<div>Messages: {agent.messages.length}</div>
<div>Running: {agent.isRunning ? "Yes" : "No"}</div>
</div>
);
}
```
### Accessing State
```tsx
function StateDisplay() {
const { agent } = useAgent();
return <pre>{JSON.stringify(agent.state, null, 2)}</pre>;
}
```
### Updating State
```tsx
function StateController() {
const { agent } = useAgent();
return (
<button onClick={() => agent.setState({ ...agent.state, count: 1 })}>
Increment
</button>
);
}
```
### Event Subscription
```tsx
import { useEffect } from "react";
import { useAgent } from "@copilotkit/react-core/v2";
import type { AgentSubscriber } from "@ag-ui/client";
function EventListener() {
const { agent } = useAgent();
useEffect(() => {
const { unsubscribe } = agent.subscribe({
onRunStartedEvent: () => console.log("Started"),
onRunFinalized: () => console.log("Finished"),
});
return unsubscribe;
}, []);
return null;
}
```
### Multiple Agents
```tsx
function MultiAgentView() {
const { agent: primary } = useAgent({ agentId: "primary" });
const { agent: support } = useAgent({ agentId: "support" });
return (
<div>
<div>Primary: {primary.messages.length}</div>
<div>Support: {support.messages.length}</div>
</div>
);
}
```
### Optimizing Re-renders
Control when your component re-renders using the `updates` parameter:
```tsx
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core/v2";
// Only re-render when messages change
function MessageCount() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnMessagesChanged]
});
return <div>Messages: {agent.messages.length}</div>;
}
// Manually manage subscriptions (no automatic re-renders)
function ManualSubscription() {
const { agent } = useAgent({ updates: [] });
useEffect(() => {
const { unsubscribe } = agent.subscribe({
onMessagesChanged: () => {
// Handle changes manually
}
});
return unsubscribe;
}, [agent]);
return <div>Manual mode</div>;
}
```
## Behavior
- **Automatic Re-renders**: Component re-renders when agent state, messages, or execution status changes (configurable via `updates` parameter)
- **Error Handling**: Throws error if no agent exists with specified `agentId`
- **State Synchronization**: State updates via `setState()` are immediately available to both app and agent
- **Event Subscriptions**: Subscribe/unsubscribe pattern for lifecycle and custom events
## Related
- [AG-UI AbstractAgent](https://docs.ag-ui.com/sdk/js/client/abstract-agent) - Full agent interface documentation
@@ -0,0 +1,199 @@
---
title: "useCopilotAction"
description: "The useCopilotAction hook allows your copilot to take action in the app."
---
<Callout type="warning">
`useCopilotAction` is still supported, but we recommend migrating to [`useFrontendTool`](/reference/v2/hooks/useFrontendTool) from the v2 API.
</Callout>
<br />
<img src="https://cdn.copilotkit.ai/docs/copilotkit/images/use-copilot-action/useCopilotAction.gif" width="500" />
`useCopilotAction` is a React hook that you can use in your application to provide
custom actions that can be called by the AI. Essentially, it allows the Copilot to
execute these actions contextually during a chat, based on the users interactions
and needs.
Here's how it works:
Use `useCopilotAction` to set up actions that the Copilot can call. To provide
more context to the Copilot, you can provide it with a `description` (for example to explain
what the action does, under which conditions it can be called, etc.).
Then you define the parameters of the action, which can be simple, e.g. primitives like strings or numbers,
or complex, e.g. objects or arrays.
Finally, you provide a `handler` function that receives the parameters and returns a result.
CopilotKit takes care of automatically inferring the parameter types, so you get type safety
and autocompletion for free.
To render a custom UI for the action, you can provide a `render()` function. This function
lets you render a custom component or return a string to display.
## Usage
### Simple Usage
```tsx
useCopilotAction({
name: "sayHello",
description: "Say hello to someone.",
parameters: [
{
name: "name",
type: "string",
description: "name of the person to say greet",
},
],
handler: async ({ name }) => {
alert(`Hello, ${name}!`);
},
});
```
## Generative UI
This hooks enables you to dynamically generate UI elements and render them in the copilot chat. For more information, check out the [Generative UI](/generative-ui/your-components/display-only) page.
## Parameters
<PropertyReference name="action" type="Action" required>
The function made available to the Copilot. See [Action](#action).
<PropertyReference name="name" type="string" required>
The name of the action.
</PropertyReference>
<PropertyReference name="handler" type="(args) => Promise<any>" required>
The handler of the action.
</PropertyReference>
<PropertyReference name="description" type="string">
A description of the action. This is used to instruct the Copilot on how to
use the action.
</PropertyReference>
<PropertyReference name="available" type="'enabled' | 'disabled' | 'remote'">
Use this property to control when the action is available to the Copilot. When set to `"remote"`, the action is
available only for remote agents.
</PropertyReference>
<PropertyReference name="followUp" type="boolean" default="true">
Whether to report the result of a function call to the LLM which will then provide a follow-up response. Pass `false` to disable
</PropertyReference>
<PropertyReference name="parameters" type="Parameter[]">
The parameters of the action. See [Parameter](#parameter).
<PropertyReference name="name" type="string" required>
The name of the parameter.
</PropertyReference>
<PropertyReference
name="type"
type="string"
required
>
The type of the argument. One of:
- `"string"`
- `"number"`
- `"boolean"`
- `"object"`
- `"object[]"`
- `"string[]"`
- `"number[]"`
- `"boolean[]"`
</PropertyReference>
<PropertyReference name="description" type="string">
A description of the argument. This is used to instruct the Copilot on what
this argument is used for.
</PropertyReference>
<PropertyReference name="enum" type="string[]">
For string arguments, you can provide an array of possible values.
</PropertyReference>
<PropertyReference name="required" type="boolean">
Whether or not the argument is required. Defaults to true.
</PropertyReference>
<PropertyReference name="attributes">
If the argument is of a complex type, i.e. `object` or `object[]`, this field
lets you define the attributes of the object. For example:
```js
{
name: "addresses",
description: "The addresses extracted from the text.",
type: "object[]",
attributes: [
{
name: "street",
type: "string",
description: "The street of the address.",
},
{
name: "city",
type: "string",
description: "The city of the address.",
},
// ...
],
}
````
</PropertyReference>
</PropertyReference>
<PropertyReference name="render" type="string | (props: ActionRenderProps<T>) => string">
Render lets you define a custom component or string to render instead of the
default. You can either pass in a string or a function that takes the following props:
<div className="ml-8">
<PropertyReference name="status" type="'inProgress' | 'executing' | 'complete'">
- `"inProgress"`: arguments are dynamically streamed to the function, allowing you to adjust your UI in real-time.
- `"executing"`: The action handler is executing.
- `"complete"`: The action handler has completed execution.
</PropertyReference>
<PropertyReference name="args" type="T">
The arguments passed to the action in real time. When the status is `"inProgress"`, they are
possibly incomplete.
</PropertyReference>
<PropertyReference name="result" type="any">
The result returned by the action. It is only available when the status is `"complete"`.
</PropertyReference>
</div>
</PropertyReference>
<PropertyReference name="renderAndWaitForResponse" type="(props: ActionRenderPropsWait<T>) => React.ReactElement">
This is similar to `render`, but provides a `respond` function in the props that you must call with the user's response. The component will remain rendered until `respond` is called. The response will be passed as the result to the action handler.
<div className="ml-8">
<PropertyReference name="status" type="'inProgress' | 'executing' | 'complete'">
- `"inProgress"`: arguments are dynamically streamed to the function, allowing you to adjust your UI in real-time.
- `"executing"`: The action handler is executing.
- `"complete"`: The action handler has completed execution.
</PropertyReference>
<PropertyReference name="args" type="T">
The arguments passed to the action in real time. When the status is `"inProgress"`, they are
possibly incomplete.
</PropertyReference>
<PropertyReference name="respond" type="(result: any) => void">
A function that must be called with the user's response. The response will be passed as the result to the action handler.
Only available when status is `"executing"`.
</PropertyReference>
<PropertyReference name="result" type="any">
The result returned by the action. It is only available when the status is `"complete"`.
</PropertyReference>
</div>
</PropertyReference>
</PropertyReference>
<PropertyReference name="dependencies" type="any[]">
An optional array of dependencies.
</PropertyReference>
@@ -0,0 +1,140 @@
---
title: "useDefaultTool"
description: "The useDefaultTool hook enables rendering of a default UI which catches any tool that does not have a specific renderer."
---
<Callout type="warning">
`useDefaultTool` is still supported, but we recommend migrating to [`useFrontendTool`](/reference/v2/hooks/useFrontendTool) from the v2 API.
</Callout>
`useDefaultTool` is a React hook that allows you to render custom UI for any tool
call that doesn't have a specific renderer
## Usage
```tsx
import { useDefaultTool } from "@copilotkit/react-core";
useDefaultTool({
render: ({ name, args, status, result }) => {
return (
<div className="p-4 border rounded my-2">
<div className="flex items-center justify-between mb-2">
<h4 className="font-semibold">{name}</h4>
<span className="text-sm text-gray-500">
{status === "inProgress" && "Running..."}
{status === "executing" && "Executing..."}
{status === "complete" && "Complete"}
</span>
</div>
{Object.keys(args).length > 0 && (
<div className="mb-2">
<p className="text-sm font-medium text-gray-600">Parameters:</p>
<pre className="text-xs bg-gray-100 p-2 rounded mt-1">
{JSON.stringify(args, null, 2)}
</pre>
</div>
)}
{status === "complete" && result && (
<div>
<p className="text-sm font-medium text-gray-600">Result:</p>
<pre className="text-xs bg-gray-100 p-2 rounded mt-1">
{JSON.stringify(result, null, 2)}
</pre>
</div>
)}
</div>
);
},
});
```
### Rendering Model Context Protocol (MCP) Tools
```tsx
import { useDefaultTool } from "@copilotkit/react-core";
// Render any MCP tool call with a custom UI
useDefaultTool({
render: ({ name, args, status, result }) => {
// Custom rendering for MCP tools
if (name.startsWith("mcp_")) {
return <MCPToolRenderer name={name} args={args} status={status} result={result} />;
}
// Default rendering for other tools
return <DefaultToolRenderer name={name} args={args} status={status} result={result} />;
},
});
```
## Parameters
<PropertyReference name="tool" type="ReactRenderToolCall<T>" required>
The tool rendering configuration object.
<PropertyReference name="render" type="React.ComponentType<RenderProps>">
A React component that renders the tool call UI. The component receives props with:
<div className="ml-8">
<PropertyReference name="status" type="'inProgress' | 'executing' | 'complete'">
- `"inProgress"`: Tool is being prepared or arguments are being streamed.
- `"executing"`: Tool is actively running.
- `"complete"`: Tool execution has finished.
</PropertyReference>
<PropertyReference name="args" type="Partial<T> | T | any">
The arguments passed to the tool. Type-safe if parameters schema is provided.
For catch-all renderers (`name: "*"`), this will be `any`.
</PropertyReference>
<PropertyReference name="result" type="any">
The result returned by the tool. Only available when status is `"complete"`.
</PropertyReference>
<PropertyReference name="name" type="string">
The actual name of the tool being executed. Particularly useful for
catch-all renderers to know which tool is being rendered.
</PropertyReference>
<PropertyReference name="description" type="string">
The description of the tool being executed.
</PropertyReference>
</div>
</PropertyReference>
</PropertyReference>
<PropertyReference name="dependencies" type="any[]">
An optional array of dependencies.
</PropertyReference>
## Common Use Cases
1. **Backend Tool Visualization**: Display progress and results of long-running backend operations
2. **Generic Tool Rendering**: Provide a fallback UI for any tool without specific rendering
3. **MCP Tool Integration**: Render Model Context Protocol tools from various sources
4. **Debugging**: Display all tool calls during development
5. **Analytics**: Track and display tool usage
## Migration from useCopilotAction
If you're migrating from `useCopilotAction` with only a `render` function:
```tsx
// Before with useCopilotAction
useCopilotAction({
render: ({ name, args, status, result }) => (
<GenericToolCall name={name} args={args} status={status} result={result} />
),
});
// After with useDefaultTool
useDefaultTool({
render: ({ name, args, status, result }) => (
<GenericToolCall name={name} args={args} status={status} result={result} />
),
});
```
The migration is straightforward - just change the hook name. The render props remain the same.
@@ -0,0 +1,187 @@
---
title: "useFrontendTool"
description: "The useFrontendTool hook allows the Copilot to execute tools in the frontend."
---
<Callout type="warning">
`useFrontendTool` is still supported, but we recommend migrating to [`useFrontendTool`](/reference/v2/hooks/useFrontendTool) from the v2 API, which uses Zod schemas for parameters.
</Callout>
`useFrontendTool` allows you to define executable actions that the AI can call with a handler function.
This is the primary way to give your AI agent the ability to perform actions in your application—whether
that's updating state, making API calls, or triggering side effects.
The hook requires three main pieces:
1. A name and description so the AI knows when to call it
2. A parameters definition describing what inputs the tool accepts
3. A handler function that executes when the AI calls the tool
Optionally, you can provide a `render` function to display custom UI showing the tool's execution
status and results in the chat interface.
## Usage
### Simple Usage
```tsx
import { useFrontendTool } from "@copilotkit/react-core";
useFrontendTool({
name: "sayHello",
description: "Say hello to someone.",
parameters: [
{
name: "name",
type: "string",
description: "name of the person to greet",
required: true,
},
],
handler: async ({ name }) => {
alert(`Hello, ${name}!`);
},
});
```
### With Custom UI Rendering
```tsx
import { useFrontendTool } from "@copilotkit/react-core";
useFrontendTool({
name: "showWeatherCard",
description: "Display weather information for a location",
parameters: [
{
name: "location",
type: "string",
description: "The location to show weather for",
required: true,
},
{
name: "temperature",
type: "number",
description: "Temperature in celsius",
required: true,
},
],
handler: async ({ location, temperature }) => {
// Fetch and return weather data
return { location, temperature, conditions: "Sunny" };
},
render: ({ args, status, result }) => {
if (status === "inProgress") {
return <div>Loading weather for {args.location}...</div>;
}
if (status === "complete" && result) {
return (
<WeatherCard
location={result.location}
temperature={result.temperature}
conditions={result.conditions}
/>
);
}
return null;
},
});
```
## Generative UI
This hook enables you to dynamically generate UI elements and render them in the copilot chat. For more information, check out the [Generative UI](/generative-ui/your-components/display-only) page.
## Migration from useCopilotAction
If you're migrating from `useCopilotAction`, here are the key differences:
1. The render component props include `name` and `description`
### Migration Example
```tsx
// Before with useCopilotAction
useCopilotAction({
name: "addTodo",
parameters: [
{
name: "text",
type: "string",
description: "The todo text",
required: true,
},
],
handler: ({ text }) => {
addTodo(text);
},
});
// After with useFrontendTool
useFrontendTool({
name: "addTodo",
parameters: [
{
name: "text",
type: "string",
description: "The todo text",
required: true,
},
],
handler: ({ text }) => {
addTodo(text);
},
});
```
## Parameters
<PropertyReference name="name" type="string" required >
The name of the tool.
</PropertyReference>
<PropertyReference name="description" type="string" >
A description of the tool. This is used to instruct the Copilot on how to use the tool.
</PropertyReference>
<PropertyReference name="parameters" type="T" >
Array of parameter definitions for the tool. Each parameter object should have:
- `name` (string): The parameter name
- `type` (string): The parameter type (e.g., "string", "number", "boolean", "string[]", "object")
- `description` (string): A description of what the parameter is for
- `required` (boolean): Whether the parameter is required
- `properties` (array, optional): For object types, define nested properties using the same schema
Simple example: `[{ name: "query", type: "string", description: "The search query", required: true }]`
Nested example:
```typescript
[
{
name: "user",
type: "object",
description: "User information",
required: true,
properties: [
{ name: "name", type: "string", description: "User's name", required: true },
{ name: "age", type: "number", description: "User's age", required: false }
]
}
]
```
</PropertyReference>
<PropertyReference name="handler" type="FrontendAction<T>['handler']" >
The handler function that executes the tool logic.
</PropertyReference>
<PropertyReference name="followUp" type="boolean" >
Whether to report the result of the tool call to the LLM which will then provide a follow-up response. Pass `false` to disable.
</PropertyReference>
<PropertyReference name="render" type="FrontendAction<T>['render']" >
A React component that renders custom UI for the tool.
</PropertyReference>
<PropertyReference name="available" type="'disabled' | 'enabled'" >
Whether the tool is available. Set to "disabled" to prevent the tool from being called.
</PropertyReference>
@@ -0,0 +1,237 @@
---
title: "useHumanInTheLoop"
description: "The useHumanInTheLoop hook enables human approval and interaction workflows."
---
<Callout type="warning">
`useHumanInTheLoop` is still supported, but we recommend migrating to [`useHumanInTheLoop`](/reference/v2/hooks/useHumanInTheLoop) from the v2 API.
</Callout>
`useHumanInTheLoop` pauses AI execution to request human input or approval. When the AI calls this
tool, it stops and waits for the user to respond through your custom UI before continuing. This is
essential for sensitive operations, confirmations, or collecting information that only the user can provide.
Unlike `useFrontendTool`, there's no handler function—instead, your render function receives a `respond`
callback that sends the user's input back to the AI. The AI execution remains paused until `respond` is called,
making this a true blocking interaction.
## Usage
### Simple Confirmation Example
```tsx
import { useHumanInTheLoop } from "@copilotkit/react-core";
useHumanInTheLoop({
name: "confirmDeletion",
description: "Ask user to confirm before deleting items",
parameters: [
{
name: "itemName",
type: "string",
description: "Name of the item to delete",
required: true,
},
{
name: "itemCount",
type: "number",
description: "Number of items to delete",
required: true,
},
],
render: ({ args, status, respond, result }) => {
if (status === "executing" && respond) {
return (
<div className="p-4 border rounded">
<p>Are you sure you want to delete {args.itemCount} {args.itemName}(s)?</p>
<div className="flex gap-2 mt-4">
<button
onClick={() => respond({ confirmed: true })}
className="bg-red-500 text-white px-4 py-2 rounded"
>
Delete
</button>
<button
onClick={() => respond({ confirmed: false })}
className="bg-gray-300 px-4 py-2 rounded"
>
Cancel
</button>
</div>
</div>
);
}
if (status === "complete" && result) {
return (
<div className="p-2 text-sm text-gray-600">
{result.confirmed ? "Items deleted" : "Deletion cancelled"}
</div>
);
}
return null;
},
});
```
### Complex Input Collection Example
```tsx
import { useHumanInTheLoop } from "@copilotkit/react-core";
import { useState } from "react";
useHumanInTheLoop({
name: "collectUserPreferences",
description: "Collect detailed preferences from the user",
parameters: [
{
name: "context",
type: "string",
description: "Context for why preferences are needed",
required: true,
},
{
name: "requiredFields",
type: "string[]",
description: "Fields to collect",
required: true,
},
],
render: ({ args, status, respond }) => {
const [preferences, setPreferences] = useState({
theme: "light",
notifications: true,
language: "en",
});
if (status === "executing" && respond) {
return (
<div className="p-4 border rounded">
<h3 className="font-bold mb-2">{args.context}</h3>
<form onSubmit={(e) => {
e.preventDefault();
respond(preferences);
}}>
<button
type="submit"
className="bg-blue-500 text-white px-4 py-2 rounded"
>
Save Preferences
</button>
</form>
</div>
);
}
return null;
},
});
```
## Best Practices
1. Always check for the `respond` function before rendering interactive elements
2. Handle all status states to provide good user feedback
3. Validate user input before calling `respond`
4. Provide clear instructions in your UI about what input is expected
5. Consider timeout scenarios for time-sensitive operations
## Migration from useCopilotAction
If you're migrating from `useCopilotAction` with `renderAndWaitForResponse`:
```tsx
// Before with useCopilotAction
useCopilotAction({
name: "confirmAction",
parameters: [
{ name: "message", type: "string", required: true },
],
renderAndWaitForResponse: ({ args, respond, status }) => {
return (
<ConfirmDialog
message={args.message}
onConfirm={() => respond(true)}
onCancel={() => respond(false)}
isActive={status === "executing"}
/>
);
},
});
// After with useHumanInTheLoop
useHumanInTheLoop({
name: "confirmAction",
parameters: [
{
name: "message",
type: "string",
description: "The message to display",
required: true,
},
],
render: ({ args, respond, status }) => {
if (status === "executing" && respond) {
return (
<ConfirmDialog
message={args.message}
onConfirm={() => respond(true)}
onCancel={() => respond(false)}
isActive={true}
/>
);
}
return null;
},
});
```
The main differences are:
1. The property is called `render` instead of `renderAndWaitForResponse`
2. You need to check for the `respond` function's existence
## Parameters
<PropertyReference name="name" type="string" required >
The name of the tool.
</PropertyReference>
<PropertyReference name="description" type="string" >
A description of the tool. This is used to instruct the Copilot on when to request human input.
</PropertyReference>
<PropertyReference name="parameters" type="T" >
Array of parameter definitions that will be passed to the render function. Each parameter object should have:
- `name` (string): The parameter name
- `type` (string): The parameter type (e.g., "string", "number", "boolean", "string[]", "object")
- `description` (string): A description of what the parameter is for
- `required` (boolean): Whether the parameter is required
- `properties` (array, optional): For object types, define nested properties using the same schema
Simple example: `[{ name: "itemName", type: "string", description: "Name of the item", required: true }]`
Nested example:
```typescript
[
{
name: "approval",
type: "object",
description: "Approval request details",
required: true,
properties: [
{ name: "action", type: "string", description: "Action requiring approval", required: true },
{ name: "reason", type: "string", description: "Reason for the action", required: false }
]
}
]
```
</PropertyReference>
<PropertyReference name="render" type="FrontendAction<T>['renderAndWaitForResponse']" required >
A React component that renders the interactive UI for human input. The component receives props including `args`, `status`, and `respond` function.
</PropertyReference>
<PropertyReference name="available" type="'disabled' | 'enabled'" >
Whether the tool is available. Set to "disabled" to prevent the tool from being called.
</PropertyReference>
@@ -0,0 +1,77 @@
---
title: "useLangGraphInterrupt"
description: "The useLangGraphInterrupt hook allows setting the generative UI to be displayed on LangGraph's Interrupt event."
---
<br />
<video src="https://cdn.copilotkit.ai/docs/copilotkit/images/coagents/interrupt-flow.mp4" className="rounded-lg shadow-xl" loop playsInline controls autoPlay muted />
<Callout type="warning">
`useLangGraphInterrupt` is still supported, but we recommend migrating to [`useHumanInTheLoop`](/reference/v2/hooks/useHumanInTheLoop) from the v2 API.
</Callout>
`useLangGraphInterrupt` is a React hook that you can use in your application to provide
custom UI to be rendered when using `interrupt` by LangGraph.
Once an Interrupt event is emitted, that hook would execute, allowing to receive user input with a user experience to your choice.
## Usage
### Simple Usage
```tsx title="app/page.tsx"
import { useLangGraphInterrupt } from "@copilotkit/react-core"; // [!code highlight]
// ...
const YourMainContent = () => {
// ...
// [!code highlight:15]
// styles omitted for brevity
useLangGraphInterrupt<string>({
render: ({ event, resolve }) => (
<div>
<p>{event.value}</p>
<form onSubmit={(e) => {
e.preventDefault();
resolve((e.target as HTMLFormElement).response.value);
}}>
<input type="text" name="response" placeholder="Enter your response" />
<button type="submit">Submit</button>
</form>
</div>
)
});
// ...
return <div>{/* ... */}</div>
}
```
## Parameters
<PropertyReference name="action" type="Action" required>
The action to perform when an Interrupt event is emitted. Either `handler` or `render` must be defined as arguments
<PropertyReference name="name" type="string" required>
The name of the action.
</PropertyReference>
<PropertyReference name="handler" type="(args: LangGraphInterruptRenderProps<T>) => any | Promise<any>">
A handler to programmatically resolve the Interrupt, or perform operations which result will be passed to the `render` method
</PropertyReference>
<PropertyReference name="render" type="(props: LangGraphInterruptRenderProps<T>) => string | React.ReactElement">
Render lets you define a custom component or string to render when an Interrupt event is emitted.
</PropertyReference>
<PropertyReference name="enabled" type="(args: { eventValue: TEventValue; agentMetadata: AgentSession }) => boolean">
Method that returns a boolean, indicating if the interrupt action should run. Useful when using multiple interrupts
</PropertyReference>
<PropertyReference name="agentId" type="string">
Optional agent ID to scope this interrupt to a specific agent. Defaults to the agent configured in the CopilotKit chat configuration.
</PropertyReference>
</PropertyReference>
<PropertyReference name="dependencies" type="any[]">
An optional array of dependencies.
</PropertyReference>
@@ -0,0 +1,131 @@
---
title: "useRenderToolCall"
description: "The useRenderToolCall hook enables rendering of backend tool calls in the frontend."
---
<Callout type="warning">
`useRenderToolCall` is still supported, but we recommend migrating to [`useRenderToolCall`](/reference/v2/hooks/useRenderToolCall) from the v2 API.
</Callout>
`useRenderToolCall` is purely a rendering hook — it displays custom UI for tool calls without executing
any logic. This is typically used to visualize backend tool executions in your chat interface, showing
users what the AI is doing behind the scenes.
This hook has no handler function. You only provide a render function that receives information about
the tool call (arguments, status, results) and displays it however you want. You can target specific
tool names or use an asterisk (`"*"`) to catch and render all tool calls.
## Usage
### Rendering a Specific Backend Tool
```tsx
import { useRenderToolCall } from "@copilotkit/react-core";
useRenderToolCall({
name: "analyzeData",
description: "Display results of data analysis",
parameters: [
{
name: "datasetName",
type: "string",
description: "Name of the dataset being analyzed",
required: true,
},
{
name: "metrics",
type: "string[]",
description: "Metrics being calculated",
required: true,
},
],
render: ({ args, status, result }) => {
if (status === "inProgress") {
return (
<div className="p-4 border rounded animate-pulse">
<h3>Analyzing {args.datasetName}...</h3>
<p>Calculating: {args.metrics?.join(", ")}</p>
</div>
);
}
if (status === "complete" && result) {
return (
<div className="p-4 border rounded bg-green-50">
<h3>Analysis Complete: {args.datasetName}</h3>
<pre className="mt-2 p-2 bg-gray-100 rounded">
{JSON.stringify(result, null, 2)}
</pre>
</div>
);
}
return null;
},
});
```
## Migration from useCopilotAction
If you're migrating from `useCopilotAction` with only a `render` function:
```tsx
// Before with useCopilotAction
useCopilotAction({
name: "showResult",
render: ({ args }) => <ResultCard {...args} />,
});
// After with useRenderToolCall
useRenderToolCall({
name: "showResult",
render: ({ args }) => <ResultCard {...args} />,
});
```
The migration is straightforward - just change the hook name. The render props remain the same.
## Parameters
<PropertyReference name="name" type="string" required >
The name of the tool to render. Use `"*"` to catch all tool calls.
</PropertyReference>
<PropertyReference name="description" type="string" >
A description of what this renderer does. Mainly for documentation purposes.
</PropertyReference>
<PropertyReference name="parameters" type="T" >
Optional array of parameter definitions. If provided, adds type safety to the args in the render function. Each parameter object should have:
- `name` (string): The parameter name
- `type` (string): The parameter type (e.g., "string", "number", "boolean", "string[]", "object")
- `description` (string): A description of what the parameter is for
- `required` (boolean): Whether the parameter is required
- `properties` (array, optional): For object types, define nested properties using the same schema
Simple example: `[{ name: "datasetName", type: "string", description: "Name of the dataset", required: true }]`
Nested example:
```typescript
[
{
name: "analysisConfig",
type: "object",
description: "Configuration for the analysis",
required: true,
properties: [
{ name: "method", type: "string", description: "Analysis method to use", required: true },
{ name: "threshold", type: "number", description: "Threshold value", required: false }
]
}
]
```
</PropertyReference>
<PropertyReference name="render" type="FrontendAction<T>['render']" >
A React component that renders the tool call UI. The component receives props with `status`, `args`, `result`, `name`, and `description`.
</PropertyReference>
<PropertyReference name="available" type="'disabled' | 'enabled'" >
Whether the renderer is available. Set to "disabled" to prevent rendering.
</PropertyReference>
@@ -0,0 +1,43 @@
---
title: "API Reference"
description: "API Reference for CopilotKit's components, classes and hooks."
---
import { LinkIcon } from "lucide-react";
<Callout type="warning">
The v1 APIs will continue to work, but we strongly recommend using or migrating to the [v2 APIs](/reference/v2).
</Callout>
<Cards>
<Card
title="UI Components"
description="See the list of all available UI components in CopilotKit."
href="/reference/v1/components/chat/CopilotChat"
icon={<LinkIcon />}
/>
<Card
title="Hooks"
description="See the list of all available hooks in CopilotKit."
href="/reference/v1/hooks/useCopilotReadable"
icon={<LinkIcon />}
/>
<Card
title="Classes"
description="See the list of all available classes in CopilotKit."
href="/reference/v1/classes/CopilotRuntime"
icon={<LinkIcon />}
/>
<Card
title="LLM Adapters"
description="See the list of all available LLM Adapters in CopilotKit."
href="/reference/v1/classes/llm-adapters/OpenAIAdapter"
icon={<LinkIcon />}
/>
<Card
title="SDKs"
description="Python and JavaScript SDKs for CopilotKit."
href="/reference/v1/sdk/python/LangGraph"
icon={<LinkIcon />}
/>
</Cards>
@@ -0,0 +1,14 @@
{
"title": "v1",
"pages": [
"index",
"---UI Components---",
"...components",
"---Hooks---",
"...hooks",
"---Classes---",
"...classes",
"---SDKs---",
"...sdk"
]
}
@@ -0,0 +1,12 @@
---
title: "LangGraph SDK"
description: "The CopilotKit LangGraph SDK for JavaScript allows you to build and run LangGraph workflows with CopilotKit."
---
{
/*
* ATTENTION! DO NOT MODIFY THIS FILE!
* This page is auto-generated. If you want to make any changes to this page, changes must be made at:
* packages/sdk-js/src/langgraph/index.ts
*/
}
@@ -0,0 +1,3 @@
{
"title": "JavaScript"
}
@@ -0,0 +1,3 @@
{
"pages": ["python", "js"]
}
@@ -0,0 +1,102 @@
---
title: "CrewAI SDK"
description: "The CopilotKit CrewAI SDK for Python allows you to build and run CrewAI agents with CopilotKit."
---
{
/*
* ATTENTION! DO NOT MODIFY THIS FILE!
* This page is auto-generated. If you want to make any changes to this page, changes must be made at:
* sdk-python/copilotkit/crewai/crewai_sdk.py
*/
}
## copilotkit_predict_state
Stream tool calls as state to CopilotKit.
To emit a tool call as streaming CrewAI state, pass the destination key in state,
the tool name and optionally the tool argument. (If you don't pass the argument name,
all arguments are emitted under the state key.)
```python
from copilotkit.crewai import copilotkit_predict_state
await copilotkit_predict_state(
{
"steps": {
"tool_name": "SearchTool",
"tool_argument": "steps",
},
}
)
```
### Parameters
<PropertyReference name="config" type="Dict[str, CopilotKitPredictStateConfig]" required>
The configuration to predict the state.
</PropertyReference>
### Returns
<PropertyReference name="returns" type="Awaitable[bool]">
Always return True.
</PropertyReference>
## copilotkit_emit_message
Manually emits a message to CopilotKit. Useful in longer running nodes to update the user.
Important: You still need to return the messages from the node.
### Examples
```python
from copilotkit.crewai import copilotkit_emit_message
message = "Step 1 of 10 complete"
await copilotkit_emit_message(message)
# Return the message from the node
return {
"messages": [AIMessage(content=message)]
}
```
### Parameters
<PropertyReference name="message" type="str" required>
The message to emit.
</PropertyReference>
### Returns
<PropertyReference name="returns" type="Awaitable[bool]">
Always return True.
</PropertyReference>
## copilotkit_emit_tool_call
Manually emits a tool call to CopilotKit.
```python
from copilotkit.crewai import copilotkit_emit_tool_call
await copilotkit_emit_tool_call(name="SearchTool", args={"steps": 10})
```
### Parameters
<PropertyReference name="name" type="str" required>
The name of the tool to emit.
</PropertyReference>
<PropertyReference name="args" type="Dict[str, Any]" required>
The arguments to emit.
</PropertyReference>
### Returns
<PropertyReference name="returns" type="Awaitable[bool]">
Always return True.
</PropertyReference>
@@ -0,0 +1,108 @@
---
title: "CrewAIAgent"
description: "CrewAIAgent lets you define your agent for use with CopilotKit."
---
{
/*
* ATTENTION! DO NOT MODIFY THIS FILE!
* This page is auto-generated. If you want to make any changes to this page, changes must be made at:
* sdk-python/copilotkit/crewai/crewai_agent.py
*/
}
## CrewAIAgent
CrewAIAgent lets you define your agent for use with CopilotKit.
To install, run:
```bash
pip install copilotkit[crewai]
```
Every agent must have the `name` and either `crew` or `flow` properties defined. An optional
`description` can also be provided. This is used when CopilotKit is dynamically routing requests
to the agent.
## Serving a Crew based agent
To serve a Crew based agent, pass in a `Crew` object to the `crew` parameter.
Note:
You need to make sure to have a `chat_llm` set on the `Crew` object.
See [the CrewAI docs](https://docs.crewai.com/concepts/cli#9-chat) for more information.
```python
from copilotkit import CrewAIAgent
CrewAIAgent(
name="email_agent_crew",
description="This crew based agent sends emails",
crew=SendEmailCrew(),
)
```
## Serving a Flow based agent
To serve a Flow based agent, pass in a `Flow` object to the `flow` parameter.
```python
CrewAIAgent(
name="email_agent_flow",
description="This flow based agent sends emails",
flow=SendEmailFlow(),
)
```
Note:
Either a `crew` or `flow` must be provided to CrewAIAgent.
### Parameters
<PropertyReference name="name" type="str" required>
The name of the agent.
</PropertyReference>
<PropertyReference name="crew" type="Crew" required>
When using a Crew based agent, pass in a `Crew` object to the `crew` parameter.
</PropertyReference>
<PropertyReference name="flow" type="Flow" required>
When using a Flow based agent, pass in a `Flow` object to the `flow` parameter.
</PropertyReference>
<PropertyReference name="description" type="Optional[str]" >
The description of the agent.
</PropertyReference>
<PropertyReference name="copilotkit_config" type="Optional[CopilotKitConfig]" >
The CopilotKit config to use with the agent.
</PropertyReference>
## CopilotKitConfig
CopilotKit config for CrewAIAgent
This is used for advanced cases where you want to customize how CopilotKit interacts with
CrewAI.
```python
# Function signatures:
def merge_state(
*,
state: dict,
messages: List[BaseMessage],
actions: List[Any],
agent_name: str
):
# ...implementation...
```
### Parameters
<PropertyReference name="merge_state" type="Callable" required>
This function lets you customize how CopilotKit merges the agent state.
</PropertyReference>
@@ -0,0 +1,203 @@
---
title: "LangGraph SDK"
description: "The CopilotKit LangGraph SDK for Python allows you to build and run LangGraph workflows with CopilotKit."
---
{
/*
* ATTENTION! DO NOT MODIFY THIS FILE!
* This page is auto-generated. If you want to make any changes to this page, changes must be made at:
* sdk-python/copilotkit/langgraph.py
*/
}
## copilotkit_customize_config
Customize the LangGraph configuration for use in CopilotKit.
To install the CopilotKit SDK, run:
```bash
pip install copilotkit
```
### Examples
Disable emitting messages and tool calls:
```python
from copilotkit.langgraph import copilotkit_customize_config
config = copilotkit_customize_config(
config,
emit_messages=False,
emit_tool_calls=False
)
```
To emit a tool call as streaming LangGraph state, pass the destination key in state,
the tool name and optionally the tool argument. (If you don't pass the argument name,
all arguments are emitted under the state key.)
```python
from copilotkit.langgraph import copilotkit_customize_config
config = copilotkit_customize_config(
config,
emit_intermediate_state=[
{
"state_key": "steps",
"tool": "SearchTool",
"tool_argument": "steps"
},
]
)
```
### Parameters
<PropertyReference name="base_config" type="Optional[RunnableConfig]" >
The LangChain/LangGraph configuration to customize. Pass None to make a new configuration.
</PropertyReference>
<PropertyReference name="emit_messages" type="Optional[bool]" >
Configure how messages are emitted. By default, all messages are emitted. Pass False to disable emitting messages.
</PropertyReference>
<PropertyReference name="emit_tool_calls" type="Optional[Union[bool, str, List[str]]]" >
Configure how tool calls are emitted. By default, all tool calls are emitted. Pass False to disable emitting tool calls. Pass a string or list of strings to emit only specific tool calls.
</PropertyReference>
<PropertyReference name="emit_intermediate_state" type="Optional[List[IntermediateStateConfig]]" >
Lets you emit tool calls as streaming LangGraph state.
</PropertyReference>
### Returns
<PropertyReference name="returns" type="RunnableConfig">
The customized LangGraph configuration.
</PropertyReference>
## copilotkit_exit
Exits the current agent after the run completes. Calling copilotkit_exit() will
not immediately stop the agent. Instead, it signals to CopilotKit to stop the agent after
the run completes.
### Examples
```python
from copilotkit.langgraph import copilotkit_exit
def my_node(state: Any):
await copilotkit_exit(config)
return state
```
### Parameters
<PropertyReference name="config" type="RunnableConfig" required>
The LangGraph configuration.
</PropertyReference>
### Returns
<PropertyReference name="returns" type="Awaitable[bool]">
Always return True.
</PropertyReference>
## copilotkit_emit_state
Emits intermediate state to CopilotKit. Useful if you have a longer running node and you want to
update the user with the current state of the node.
### Examples
```python
from copilotkit.langgraph import copilotkit_emit_state
for i in range(10):
await some_long_running_operation(i)
await copilotkit_emit_state(config, {"progress": i})
```
### Parameters
<PropertyReference name="config" type="RunnableConfig" required>
The LangGraph configuration.
</PropertyReference>
<PropertyReference name="state" type="Any" required>
The state to emit (Must be JSON serializable).
</PropertyReference>
### Returns
<PropertyReference name="returns" type="Awaitable[bool]">
Always return True.
</PropertyReference>
## copilotkit_emit_message
Manually emits a message to CopilotKit. Useful in longer running nodes to update the user.
Important: You still need to return the messages from the node.
### Examples
```python
from copilotkit.langgraph import copilotkit_emit_message
message = "Step 1 of 10 complete"
await copilotkit_emit_message(config, message)
# Return the message from the node
return {
"messages": [AIMessage(content=message)]
}
```
### Parameters
<PropertyReference name="config" type="RunnableConfig" required>
The LangGraph configuration.
</PropertyReference>
<PropertyReference name="message" type="str" required>
The message to emit.
</PropertyReference>
### Returns
<PropertyReference name="returns" type="Awaitable[bool]">
Always return True.
</PropertyReference>
## copilotkit_emit_tool_call
Manually emits a tool call to CopilotKit.
```python
from copilotkit.langgraph import copilotkit_emit_tool_call
await copilotkit_emit_tool_call(config, name="SearchTool", args={"steps": 10})
```
### Parameters
<PropertyReference name="config" type="RunnableConfig" required>
The LangGraph configuration.
</PropertyReference>
<PropertyReference name="name" type="str" required>
The name of the tool to emit.
</PropertyReference>
<PropertyReference name="args" type="Dict[str, Any]" required>
The arguments to emit.
</PropertyReference>
### Returns
<PropertyReference name="returns" type="Awaitable[bool]">
Always return True.
</PropertyReference>
@@ -0,0 +1,102 @@
---
title: "LangGraphAgent"
description: "LangGraphAgent lets you define your agent for use with CopilotKit."
---
{
/*
* ATTENTION! DO NOT MODIFY THIS FILE!
* This page is auto-generated. If you want to make any changes to this page, changes must be made at:
* sdk-python/copilotkit/langgraph_agent.py
*/
}
## LangGraphAgent
LangGraphAgent lets you define your agent for use with CopilotKit.
To install, run:
```bash
pip install copilotkit
```
### Examples
Every agent must have the `name` and `graph` properties defined. An optional `description`
can also be provided. This is used when CopilotKit is dynamically routing requests to the
agent.
```python
from copilotkit import LangGraphAgent
LangGraphAgent(
name="email_agent",
description="This agent sends emails",
graph=graph,
)
```
If you have a custom LangGraph/LangChain config that you want to use with the agent, you can
pass it in as the `langgraph_config` parameter.
```python
LangGraphAgent(
...
langgraph_config=config,
)
```
### Parameters
<PropertyReference name="name" type="str" required>
The name of the agent.
</PropertyReference>
<PropertyReference name="graph" type="CompiledStateGraph" required>
The LangGraph graph to use with the agent.
</PropertyReference>
<PropertyReference name="description" type="Optional[str]" >
The description of the agent.
</PropertyReference>
<PropertyReference name="langgraph_config" type="Optional[RunnableConfig]" >
The LangGraph/LangChain config to use with the agent.
</PropertyReference>
<PropertyReference name="copilotkit_config" type="Optional[CopilotKitConfig]" >
The CopilotKit config to use with the agent.
</PropertyReference>
## CopilotKitConfig
CopilotKit config for LangGraphAgent
This is used for advanced cases where you want to customize how CopilotKit interacts with
LangGraph.
```python
# Function signatures:
def merge_state(
*,
state: dict,
messages: List[BaseMessage],
actions: List[Any],
agent_name: str
):
# ...implementation...
def convert_messages(messages: List[Message]):
# ...implementation...
```
### Parameters
<PropertyReference name="merge_state" type="Callable" required>
This function lets you customize how CopilotKit merges the agent state.
</PropertyReference>
<PropertyReference name="convert_messages" type="Callable" required>
Use this function to customize how CopilotKit converts its messages to LangChain messages.`
</PropertyReference>
@@ -0,0 +1,181 @@
---
title: "Remote Endpoints"
description: "CopilotKit Remote Endpoints allow you to connect actions and agents written in Python to your CopilotKit application."
---
{
/*
* ATTENTION! DO NOT MODIFY THIS FILE!
* This page is auto-generated. If you want to make any changes to this page, changes must be made at:
* sdk-python/copilotkit/sdk.py
*/
}
## CopilotKitRemoteEndpoint
CopilotKitRemoteEndpoint lets you connect actions and agents written in Python to your
CopilotKit application.
To install CopilotKit for Python, run:
```bash
pip install copilotkit
# or to include crewai
pip install copilotkit[crewai]
```
## Adding actions
In this example, we provide a simple action to the Copilot:
```python
from copilotkit import CopilotKitRemoteEndpoint, Action
sdk = CopilotKitRemoteEndpoint(
actions=[
Action(
name="greet_user",
handler=greet_user_handler,
description="Greet the user",
parameters=[
{
"name": "name",
"type": "string",
"description": "The name of the user"
}
]
)
]
)
```
You can also dynamically build actions by providing a callable that returns a list of actions.
In this example, we use "name" from the `properties` object to parameterize the action handler.
```python
from copilotkit import CopilotKitRemoteEndpoint, Action
sdk = CopilotKitRemoteEndpoint(
actions=lambda context: [
Action(
name="greet_user",
handler=make_greet_user_handler(context["properties"]["name"]),
description="Greet the user"
)
]
)
```
Using the same approach, you can restrict the actions available to the Copilot:
```python
from copilotkit import CopilotKitRemoteEndpoint, Action
sdk = CopilotKitRemoteEndpoint(
actions=lambda context: (
[action_a, action_b] if is_admin(context["properties"]["token"]) else [action_a]
)
)
```
## Adding agents
Serving agents works in a similar way to serving actions:
```python
from copilotkit import CopilotKitRemoteEndpoint, LangGraphAgent
from my_agent.agent import graph
sdk = CopilotKitRemoteEndpoint(
agents=[
LangGraphAgent(
name="email_agent",
description="This agent sends emails",
graph=graph,
)
]
)
```
To dynamically build agents, provide a callable that returns a list of agents:
```python
from copilotkit import CopilotKitRemoteEndpoint, LangGraphAgent
from my_agent.agent import graph
sdk = CopilotKitRemoteEndpoint(
agents=lambda context: [
LangGraphAgent(
name="email_agent",
description="This agent sends emails",
graph=graph,
langgraph_config={
"token": context["properties"]["token"]
}
)
]
)
```
To restrict the agents available to the Copilot, simply return a different list of agents based on the `context`:
```python
from copilotkit import CopilotKitRemoteEndpoint
from my_agents import agent_a, agent_b, is_admin
sdk = CopilotKitRemoteEndpoint(
agents=lambda context: (
[agent_a, agent_b] if is_admin(context["properties"]["token"]) else [agent_a]
)
)
```
## Serving the CopilotKit SDK
To serve the CopilotKit SDK, you can use the `add_fastapi_endpoint` function from the `copilotkit.integrations.fastapi` module:
```python
from copilotkit.integrations.fastapi import add_fastapi_endpoint
from fastapi import FastAPI
app = FastAPI()
sdk = CopilotKitRemoteEndpoint(...)
add_fastapi_endpoint(app, sdk, "/copilotkit")
def main():
uvicorn.run(
"your_package:app",
host="0.0.0.0",
port=8000,
reload=True,
)
```
### Parameters
<PropertyReference name="actions" type="Optional[Union[List[Action], Callable[[CopilotKitContext], List[Action]]]]" >
The actions to make available to the Copilot.
</PropertyReference>
<PropertyReference name="agents" type="Optional[Union[List[Agent], Callable[[CopilotKitContext], List[Agent]]]]" >
The agents to make available to the Copilot.
</PropertyReference>
## CopilotKitContext
CopilotKit Context
### Parameters
<PropertyReference name="properties" type="Any" required>
The properties provided to the frontend via `<CopilotKit properties={...} />`
</PropertyReference>
<PropertyReference name="frontend_url" type="Optional[str]" >
The current URL of the frontend
</PropertyReference>
<PropertyReference name="headers" type="Mapping[str, str]" required>
The headers of the request
</PropertyReference>
@@ -0,0 +1,9 @@
{
"pages": [
"RemoteEndpoints",
"LangGraphAgent",
"LangGraph",
"CrewAIAgent",
"CrewAI"
]
}
+241 -49
View File
@@ -1,38 +1,115 @@
// Shared helpers for walking the `src/content/reference/` tree. Used by
// both /reference (index page) and /reference/[...slug] so the two stay
// in sync: same subdirs, same recursive traversal, same gray-matter
// handling, same caching behavior.
// Shared helpers for walking and resolving the `src/content/reference/`
// tree. The v2 reference lives at the root for backwards-compatible
// `/reference/<slug>` URLs and is also exposed as `/reference/v2/<slug>`.
// The v1 reference is nested under `src/content/reference/v1`.
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import type * as PageTree from "fumadocs-core/page-tree";
import { safeExistsSync, safeReadFileSync } from "@/lib/safe-fs";
export const REFERENCE_CONTENT_DIR = path.join(
process.cwd(),
"src/content/reference",
);
// Top-level reference categories we index. Anything outside this list is
// ignored (e.g. a stray snippet file at the root).
export const REFERENCE_SUBDIRS = ["components", "hooks"] as const;
export type ReferenceSubdir = (typeof REFERENCE_SUBDIRS)[number];
export const REFERENCE_VERSIONS = ["v2", "v1"] as const;
export type ReferenceVersion = (typeof REFERENCE_VERSIONS)[number];
export const REFERENCE_CATEGORIES = [
"Components",
"Hooks",
"Classes",
"SDKs",
] as const;
export type ReferenceCategory = (typeof REFERENCE_CATEGORIES)[number];
type ReferenceSubdir = "components" | "hooks" | "classes" | "sdk";
const VERSION_SUBDIRS: Record<ReferenceVersion, ReferenceSubdir[]> = {
v2: ["components", "hooks", "sdk"],
v1: ["components", "hooks", "classes", "sdk"],
};
const CATEGORY_BY_SUBDIR: Record<ReferenceSubdir, ReferenceCategory> = {
components: "Components",
hooks: "Hooks",
classes: "Classes",
sdk: "SDKs",
};
export type ReferenceItem = {
/** subdir-relative slug, e.g. `components/chat` or `components/inputs/textarea`. */
/** Version-relative slug, e.g. `components/chat` or `hooks/useAgent`. */
slug: string;
title: string;
description?: string;
category: "Components" | "Hooks";
category: ReferenceCategory;
version: ReferenceVersion;
url: string;
};
function categoryFor(subdir: ReferenceSubdir): "Components" | "Hooks" {
return subdir === "components" ? "Components" : "Hooks";
export type ResolvedReferencePage = {
version: ReferenceVersion;
pageSlug: string;
contentSlug: string;
raw: string;
};
function isProd(): boolean {
return process.env.NODE_ENV === "production";
}
function versionDir(version: ReferenceVersion): string {
return version === "v1"
? path.join(REFERENCE_CONTENT_DIR, "v1")
: REFERENCE_CONTENT_DIR;
}
function versionRelativePrefix(version: ReferenceVersion): string {
return version === "v1" ? "v1/" : "";
}
export function referenceHref(
version: ReferenceVersion,
pageSlug?: string,
): string {
const cleanSlug = pageSlug?.replace(/^\/+|\/+$/g, "");
const suffix = cleanSlug ? `/${cleanSlug}` : "";
return `/reference/${version}${suffix}`;
}
function contentSlugForPage(
version: ReferenceVersion,
pageSlug: string,
): string {
const prefix = versionRelativePrefix(version);
return `${prefix}${pageSlug || "index"}`;
}
function pageExists(version: ReferenceVersion, pageSlug: string): boolean {
const contentSlug = contentSlugForPage(version, pageSlug);
return (
safeExistsSync(REFERENCE_CONTENT_DIR, `${contentSlug}.mdx`) ||
safeExistsSync(REFERENCE_CONTENT_DIR, `${contentSlug}/index.mdx`)
);
}
export function referenceVersionHref(
version: ReferenceVersion,
currentPageSlug?: string,
): string {
const cleanSlug = currentPageSlug?.replace(/^\/+|\/+$/g, "") ?? "";
return referenceHref(
version,
cleanSlug && pageExists(version, cleanSlug) ? cleanSlug : undefined,
);
}
/**
* Recursively collect all `.mdx` files under `dir` and return their paths
* relative to `dir` (without the `.mdx` extension). Silently skips
* unreadable subdirectories so a single EACCES doesn't break the build.
* Recursively collect `.mdx` files under `dir` and return paths relative
* to `dir` without the `.mdx` extension. Directory index pages are kept
* as `folder/index` here and normalized later.
*/
function walkMdx(dir: string, prefix: string = ""): string[] {
let entries: fs.Dirent[];
@@ -42,9 +119,10 @@ function walkMdx(dir: string, prefix: string = ""): string[] {
console.error(`[reference-items] Failed to read dir ${dir}:`, err);
return [];
}
const out: string[] = [];
for (const entry of entries) {
if (entry.name.startsWith(".")) continue;
if (entry.name.startsWith(".") || entry.name === "meta.json") continue;
const childAbs = path.join(dir, entry.name);
const childRel = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
@@ -56,17 +134,32 @@ function walkMdx(dir: string, prefix: string = ""): string[] {
return out;
}
/**
* Load items from a single reference subdir, recursing into subfolders.
* Malformed frontmatter on any file is logged and skipped — we never
* crash the whole index just because one page has a bad YAML block.
*/
function loadSubdirItems(subdir: ReferenceSubdir): ReferenceItem[] {
const dir = path.join(REFERENCE_CONTENT_DIR, subdir);
function normalizeRouteSlug(subdir: ReferenceSubdir, relSlug: string): string {
const normalized = relSlug.endsWith("/index")
? relSlug.slice(0, -"/index".length)
: relSlug;
return normalized === "index" ? subdir : `${subdir}/${normalized}`;
}
function fallbackTitle(routeSlug: string): string {
return routeSlug.split("/").filter(Boolean).pop() ?? routeSlug;
}
function loadSubdirItems(
version: ReferenceVersion,
subdir: ReferenceSubdir,
): ReferenceItem[] {
const dir = path.join(versionDir(version), subdir);
if (!fs.existsSync(dir)) return [];
const items: ReferenceItem[] = [];
const seenSlugs = new Set<string>();
for (const relSlug of walkMdx(dir)) {
const routeSlug = normalizeRouteSlug(subdir, relSlug);
if (seenSlugs.has(routeSlug)) continue;
seenSlugs.add(routeSlug);
const filePath = path.join(dir, `${relSlug}.mdx`);
let raw: string;
try {
@@ -75,6 +168,7 @@ function loadSubdirItems(subdir: ReferenceSubdir): ReferenceItem[] {
console.error(`[reference-items] Failed to read ${filePath}:`, err);
continue;
}
let data: Record<string, unknown> = {};
try {
({ data } = matter(raw));
@@ -85,50 +179,148 @@ function loadSubdirItems(subdir: ReferenceSubdir): ReferenceItem[] {
);
continue;
}
const fallbackTitle = relSlug.split("/").pop() ?? relSlug;
items.push({
slug: `${subdir}/${relSlug}`,
slug: routeSlug,
title:
typeof data.title === "string" && data.title.length > 0
? data.title
: fallbackTitle,
: fallbackTitle(routeSlug),
description:
typeof data.description === "string" ? data.description : undefined,
category: categoryFor(subdir),
category: CATEGORY_BY_SUBDIR[subdir],
version,
url: referenceHref(version, routeSlug),
});
}
return items;
}
// In-memory cache — keyed by subdir, rebuilt once per process in prod. In
// dev we skip the cache so MDX edits show up without a server restart.
const __itemsCache = new Map<ReferenceSubdir, ReferenceItem[]>();
function isProd(): boolean {
return process.env.NODE_ENV === "production";
}
const itemsCache = new Map<string, ReferenceItem[]>();
export function loadReferenceItems(subdir: ReferenceSubdir): ReferenceItem[] {
export function loadReferenceItems(
version: ReferenceVersion,
subdir: ReferenceSubdir,
): ReferenceItem[] {
const cacheKey = `${version}:${subdir}`;
if (isProd()) {
const cached = __itemsCache.get(subdir);
const cached = itemsCache.get(cacheKey);
if (cached) return cached;
}
const items = loadSubdirItems(subdir);
if (isProd()) __itemsCache.set(subdir, items);
const items = loadSubdirItems(version, subdir);
if (isProd()) itemsCache.set(cacheKey, items);
return items;
}
export function loadAllReferenceItems(): ReferenceItem[] {
return REFERENCE_SUBDIRS.flatMap((s) => loadReferenceItems(s));
export function loadReferenceVersionItems(
version: ReferenceVersion,
): ReferenceItem[] {
return VERSION_SUBDIRS[version].flatMap((subdir) =>
loadReferenceItems(version, subdir),
);
}
/**
* For `generateStaticParams`: return every reference page as its Next.js
* catch-all slug array. Recursive (unlike the previous one-level-only
* implementation), so subfolder docs like `components/inputs/textarea`
* are statically generated too.
*/
export function referenceStaticParams(): { slug: string[] }[] {
return loadAllReferenceItems().map((item) => ({
slug: item.slug.split("/"),
}));
export function buildReferencePageTree(
version: ReferenceVersion,
): PageTree.Root {
const allItems = loadReferenceVersionItems(version);
return {
name: "Reference",
children: REFERENCE_CATEGORIES.flatMap((category) => {
const categoryItems = allItems.filter(
(item) => item.category === category,
);
if (categoryItems.length === 0) return [];
return [
{ type: "separator" as const, name: category },
...categoryItems.map(
(item): PageTree.Item => ({
type: "page",
name: item.title,
url: item.url,
}),
),
];
}),
};
}
function splitVersionedSlug(slugPath: string): {
version: ReferenceVersion;
pageSlug: string;
} {
if (slugPath === "v1" || slugPath.startsWith("v1/")) {
return { version: "v1", pageSlug: slugPath.replace(/^v1\/?/, "") };
}
if (slugPath === "v2" || slugPath.startsWith("v2/")) {
return { version: "v2", pageSlug: slugPath.replace(/^v2\/?/, "") };
}
return { version: "v2", pageSlug: slugPath };
}
export function resolveReferencePage(
slug: string[],
): ResolvedReferencePage | null {
const slugPath = slug.join("/");
const { version, pageSlug } = splitVersionedSlug(slugPath);
const contentSlug = contentSlugForPage(version, pageSlug);
const raw =
safeReadFileSync(REFERENCE_CONTENT_DIR, `${contentSlug}.mdx`) ??
safeReadFileSync(REFERENCE_CONTENT_DIR, `${contentSlug}/index.mdx`);
if (raw === null) return null;
return {
version,
pageSlug,
contentSlug,
raw,
};
}
export function readReferenceIndexDescription(
version: ReferenceVersion,
): string {
const fallback =
version === "v1"
? "API Reference for CopilotKit's components, classes and hooks."
: "API Reference for the next-generation CopilotKit React API.";
const raw = safeReadFileSync(
REFERENCE_CONTENT_DIR,
`${versionRelativePrefix(version)}index.mdx`,
);
if (raw === null) return fallback;
try {
const { data } = matter(raw);
return typeof data.description === "string" && data.description.length > 0
? data.description
: fallback;
} catch (err) {
console.error(
`[reference] Failed to parse ${version} index frontmatter:`,
err,
);
return fallback;
}
}
export function referenceStaticParams(): { slug: string[] }[] {
const params = new Map<string, string[]>();
const add = (slug: string[]) => params.set(slug.join("/"), slug);
add(["v1"]);
add(["v2"]);
for (const version of REFERENCE_VERSIONS) {
for (const item of loadReferenceVersionItems(version)) {
add([version, ...item.slug.split("/")]);
if (version === "v2") {
add(item.slug.split("/"));
}
}
}
return [...params.values()].map((slug) => ({ slug }));
}
@@ -984,11 +984,6 @@ const WILDCARD_REDIRECTS: RedirectEntry[] = [
},
{ id: "T1-unscoped-root", source: "/tutorials", destination: "/" },
// Category 1: Pattern rules (bulk coverage)
{
id: "P10",
source: "/reference/v1/:path*",
destination: "/reference/v2/:path*",
},
{
id: "P11",
source: "/guides/:path*",