docs(shell-docs): Slack platform quickstart + Bots API reference

- New Platforms entry: /platform/slack quickstart — manifest-based app
  creation, Socket Mode tokens, minimal createBot bot run with tsx,
  interactive JSX with inline onClick, slash commands, production split
- New "Bots" SDK tab in the reference picker with per-symbol pages for
  @copilotkit/bot, @copilotkit/bot-ui, and @copilotkit/bot-slack
  (Components / Functions / Classes / Types)
- Rename reference picker labels to React (V2) / React (V1)
- Remove the retired /reference/sdk pages (LangGraph/CrewAI SDK,
  Remote Endpoints); search/sitemap/llms indexes derive from the
  content tree, so they de-index with the deletion
- Retarget the one inbound link to its /reference/v1 copy

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tyler Slaton
2026-06-10 15:46:16 -07:00
parent 744e095243
commit a8d43a9c2e
41 changed files with 2264 additions and 632 deletions
@@ -13,7 +13,9 @@ 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}()`;
if (item.category === "Hooks" || item.category === "Functions") {
return `${item.title}()`;
}
return item.title;
}
@@ -41,6 +43,12 @@ const SDK_CHOICES: { name: string; description: string; href: string }[] = [
"The framework-agnostic @copilotkit/core client — runs anywhere JavaScript runs.",
href: referenceVersionHref("core"),
},
{
name: "Bots",
description:
"The bot stack — createBot, JSX message components, and the Slack adapter.",
href: referenceVersionHref("bot"),
},
];
export default function ReferencePage() {
@@ -15,9 +15,10 @@ export type ReferenceVersionOption = {
// The selector now switches between SDKs, not just React versions. Labels
// are user-facing; keep them in sync with REFERENCE_VERSIONS.
const VERSION_LABELS: Record<ReferenceVersion, string> = {
v2: "React v2",
v1: "React v1",
v2: "React (V2)",
v1: "React (V1)",
core: "Core (TypeScript)",
bot: "Bots",
};
export function ReferenceVersionSelector({
@@ -47,6 +47,7 @@
"---Deploy---",
"...deploy",
"---Platforms---",
"slack",
"react-native",
"---Other---",
"...(other)",
@@ -0,0 +1,266 @@
---
title: Slack
description: Build an AI Slack bot with CopilotKit — createBot, the Slack adapter over Socket Mode, agent runs with thread.runAgent, and interactive JSX messages rendered as Block Kit.
icon: "lucide/Slack"
hideTOC: true
---
This guide takes you from zero to a Slack bot you can @-mention in a channel, then adds an interactive button card. You write handlers in TypeScript, the agent's replies stream into the thread, and rich messages are JSX that the adapter renders to Block Kit (Slack's message-UI format). No public URL needed.
## Prerequisites
- Node.js 20+
- A Slack workspace where you can install apps
- An OpenAI API key (or Anthropic/Google — any model the [built-in agent](/build-with-agents) supports)
## Getting started
<Steps>
<Step>
### Create the Slack app from a manifest
The manifest declares everything the bot needs — scopes, events, Socket Mode, a `/agent` slash command — in one shot.
1. Open [api.slack.com/apps?new_app=1](https://api.slack.com/apps?new_app=1) and choose **From a manifest**.
2. Pick your workspace.
3. Switch the editor to the **YAML** tab (it defaults to JSON) and paste the contents of [`examples/slack/slack-app-manifest.yaml`](https://github.com/CopilotKit/CopilotKit/blob/main/examples/slack/slack-app-manifest.yaml).
4. Review and create the app.
<Callout type="warn" title="If validation fails on assistant:write">
Delete these two lines if the manifest you pasted has them — Slack rejects `assistant:write` unless the app also declares an `assistant_view` feature block: `- assistant:write` (under `oauth_config.scopes.bot`) and `- assistant_thread_started` (under `settings.event_subscriptions.bot_events`).
</Callout>
</Step>
<Step>
### Install the app and copy both tokens
The bot needs two tokens:
1. **Bot token (`xoxb-…`)** — under **OAuth & Permissions**, click **Install to Workspace** and approve. Copy the **Bot User OAuth Token** that appears after the install.
2. **App-level token (`xapp-…`)** — under **Basic Information → App-Level Tokens**, click **Generate Token and Scopes**, name it anything, add the `connections:write` scope, and generate. Copy the token.
<Callout type="info" title="No public URL needed">
Socket Mode opens an outbound WebSocket to Slack — no public URL, no ngrok, works from your laptop.
</Callout>
</Step>
<Step>
### Scaffold the project
```bash
mkdir my-slack-bot && cd my-slack-bot
npm init -y && npm pkg set type=module
```
Install the bot packages, plus `@copilotkit/runtime` for the in-process agent and `tsx` to run TypeScript directly:
<Tabs groupId="package-manager" items={['npm', 'pnpm', 'yarn']}>
<Tab value="npm">
```bash
npm install @copilotkit/bot @copilotkit/bot-ui @copilotkit/bot-slack @copilotkit/runtime
npm install -D tsx typescript @types/node
```
</Tab>
<Tab value="pnpm">
```bash
pnpm add @copilotkit/bot @copilotkit/bot-ui @copilotkit/bot-slack @copilotkit/runtime
pnpm add -D tsx typescript @types/node
```
</Tab>
<Tab value="yarn">
```bash
yarn add @copilotkit/bot @copilotkit/bot-ui @copilotkit/bot-slack @copilotkit/runtime
yarn add -D tsx typescript @types/node
```
</Tab>
</Tabs>
Then create a `tsconfig.json` that points the JSX factory at `@copilotkit/bot-ui` — this is what makes `<Message>` / `<Button>` statically type-checked bot UI instead of React:
```json title="tsconfig.json"
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "@copilotkit/bot-ui"
},
"include": ["bot.tsx"]
}
```
<Callout type="info" title="ESM only">
The bot packages are ESM-only — `"type": "module"` (set above) is required.
</Callout>
</Step>
<Step>
### Write the bot
The smallest working bot is `createBot` + the Slack adapter + one `onMention` handler that runs the agent. For the quickstart, the agent — CopilotKit's `BuiltInAgent` — runs inside the same process, served on a local port the bot connects to, so one command starts everything:
```tsx title="bot.tsx"
import { createServer } from "node:http";
import { createBot } from "@copilotkit/bot";
import { slack, SanitizingHttpAgent } from "@copilotkit/bot-slack"; // [!code highlight]
import { BuiltInAgent, CopilotSseRuntime } from "@copilotkit/runtime/v2";
import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node";
// The agent — runs in-process for the quickstart.
const runtime = new CopilotSseRuntime({
agents: {
assistant: new BuiltInAgent({
model: "openai/gpt-5.5", // reads OPENAI_API_KEY
prompt: "You are a helpful Slack assistant. Keep replies short.",
}),
},
});
createServer(
createCopilotNodeListener({ runtime, basePath: "/api/copilotkit" }),
).listen(8200);
// The bot: the Slack adapter + one handler.
const bot = createBot({
adapters: [
// [!code highlight:4]
slack({
botToken: process.env.SLACK_BOT_TOKEN!, // xoxb-…
appToken: process.env.SLACK_APP_TOKEN!, // xapp-…
}),
],
// One agent connection per Slack conversation.
agent: (threadId) => {
const agent = new SanitizingHttpAgent({
url: "http://localhost:8200/api/copilotkit/agent/assistant/run",
});
agent.threadId = threadId;
return agent;
},
});
bot.onMention(async ({ thread }) => {
await thread.runAgent(); // [!code highlight]
});
await bot.start();
console.log("⚡ Bot connected over Socket Mode");
```
`thread.runAgent()` streams the agent's reply into the Slack thread, editing the message in place as tokens arrive.
</Step>
<Step>
### Run it
```bash
export SLACK_BOT_TOKEN=xoxb-…
export SLACK_APP_TOKEN=xapp-…
export OPENAI_API_KEY=sk-…
npx tsx bot.tsx
```
You should see `⚡ Bot connected over Socket Mode` in the terminal. Now **invite the bot** to a channel and mention it — the bot's name comes from the manifest (if autocomplete can't find it, check **App Home** → *Default username*):
```
/invite @YourBot
@YourBot what can you do?
```
<Accordions className="mb-4">
<Accordion title="Troubleshooting">
- **Mentioning the bot does nothing** — `app_mention` only fires in channels the bot is a **member** of: `/invite` it first. Also check the process is running and both tokens are set.
- **`@`-autocomplete doesn't find the bot** — search by the bot user's *Default username* (visible under **App Home**), not the app's display name. Identity changes only propagate when you **reinstall** the app; in stubborn cases a full uninstall → reinstall is needed, which **rotates the `xoxb-` token** — update your env when it does.
- **`bot.start()` fails with an auth error** — the `xoxb-` token is wrong or was rotated by a reinstall; copy the current one from OAuth & Permissions.
- **Slash command does nothing** — the command isn't declared in the Slack app config (see the last step), or the process isn't running.
</Accordion>
</Accordions>
</Step>
<Step>
### Post interactive UI
Replies don't have to be text. Messages are authored as JSX from the [`@copilotkit/bot-ui` vocabulary](/reference/bot/components/Message) — including buttons with **inline `onClick` handlers**. Replace the `onMention` handler from the previous step:
```tsx title="bot.tsx"
import { Message, Header, Section, Actions, Button } from "@copilotkit/bot-ui"; // [!code highlight]
bot.onMention(async ({ thread, message }) => {
if (message.text.toLowerCase().includes("deploy")) {
await thread.post(
<Message accent="#27AE60">
<Header>Deploy v1.4.2</Header>
<Section>Ship **v1.4.2** to production?</Section>
<Actions>
{/* [!code highlight:6] */}
<Button
style="primary"
onClick={async ({ thread }) => {
await thread.post("🚀 Shipping!");
}}
>
Ship it
</Button>
<Button
onClick={async ({ thread }) => {
await thread.post("Standing down.");
}}
>
Cancel
</Button>
</Actions>
</Message>,
);
return;
}
await thread.runAgent();
});
```
Mention the bot with "deploy" in the message and click the buttons. Your handler code never leaves your process — Slack only sees an opaque action id.
<Callout type="warn" title="Buttons expire on restart">
The default action store is **in-memory**: after a process restart, clicks on old buttons are acknowledged but ignored. For buttons that survive restarts, plug a durable store (Redis, a database) into `createBot({ actionStore })` — see the [ActionStore contract](/reference/bot/types/ActionStore).
</Callout>
</Step>
<Step>
### Add a slash command
The manifest already declares `/agent` — register its handler above `bot.start()`. Slash-command text never appears in the channel, so pass it to the agent explicitly with `prompt`:
```tsx title="bot.tsx"
bot.onCommand("agent", async ({ thread, text }) => {
await thread.runAgent({ prompt: text }); // [!code highlight]
});
```
<Callout type="warn" title="Declare commands in the Slack app config">
Slack silently drops undeclared commands — declare new ones in the manifest's `slash_commands` (or **Slash Commands** in the app settings) first.
</Callout>
</Step>
</Steps>
## Split the bot and the agent
The bot and its agent talk over [AG-UI](https://docs.ag-ui.com) — an open protocol for agent ↔ frontend communication — so they don't have to share a process. The production shape is two services joined by a URL: move the `CopilotSseRuntime` block into its own process (or use any existing AG-UI endpoint — a deployed CopilotKit runtime, LangGraph, …) and point the bot at it via env, exactly how the full [on-call triage example](https://github.com/CopilotKit/CopilotKit/tree/main/examples/slack) is wired:
```tsx title="bot.tsx"
agent: (threadId) => {
const agent = new SanitizingHttpAgent({ url: process.env.AGENT_URL! }); // [!code highlight]
agent.threadId = threadId;
return agent;
},
```
[`SanitizingHttpAgent`](/reference/bot/slack/SanitizingHttpAgent) is an `HttpAgent` that tolerates the event streams real agent backends emit — use it over the stock `HttpAgent` when connecting to a remote runtime.
## Known limitations (v1)
- **Single workspace** — one bot token; no OAuth/multi-workspace install flow
- **In-memory action store by default** — inline button handlers expire on restart unless you provide a durable `ActionStore`
- **No modals or reactions** — the adapter doesn't open modals or add reactions yet
- **Replies only** — the bot answers turns it's part of (mentions, its threads, DMs); it doesn't post proactively
## Next steps
- **API reference:** the [Bots reference](/reference/bot) — [createBot](/reference/bot/functions/createBot), the [Thread API](/reference/bot/classes/Thread), [tools](/reference/bot/functions/defineBotTool) & [commands](/reference/bot/functions/defineBotCommand), the [component vocabulary](/reference/bot/components/Message), and the [Slack adapter](/reference/bot/slack)
- **Full example:** [on-call triage bot over Linear + Notion MCP](https://github.com/CopilotKit/CopilotKit/tree/main/examples/slack) — tools, human-in-the-loop, slash commands, file uploads
@@ -71,7 +71,7 @@ If you're getting a *"CopilotKit's Remote Endpoint not found"* error, the `/info
<Accordions>
<Accordion title="Check your FastAPI / backend setup">
Confirm the CopilotKit SDK is mounted. If you're using Python + FastAPI, follow the [Remote Python Endpoint](/reference/sdk/python/RemoteEndpoints) guide.
Confirm the CopilotKit SDK is mounted. If you're using Python + FastAPI, follow the [Remote Python Endpoint](/reference/v1/sdk/python/RemoteEndpoints) guide.
</Accordion>
<Accordion title="Test the /info endpoint directly">
@@ -0,0 +1,129 @@
---
title: "Thread"
description: "The per-conversation handle — post and stream messages, run the agent, block on a human choice, and reach platform power through capability-gated methods."
---
## Overview
A `Thread` is the per-conversation handle passed to every handler, tool context, and interaction context. It posts UI (JSX from the [component vocabulary](/reference/bot/components/Message) or plain strings), drives the agent run loop, resolves human-in-the-loop choices, and exposes platform power through **capability-gated** methods that degrade gracefully on surfaces that don't support them.
```ts
interface Thread {
readonly platform: string;
post(ui: Renderable): Promise<MessageRef>;
update(ref: MessageRef, ui: Renderable): Promise<MessageRef>;
delete(ref: MessageRef): Promise<void>;
stream(src: string | AsyncIterable<string>): Promise<MessageRef>;
runAgent(input?: {
context?: ContextEntry[];
tools?: BotTool[];
prompt?: string;
}): Promise<MessageRef | undefined>;
resume(value: unknown): Promise<MessageRef | undefined>;
awaitChoice<T = unknown>(ui: Renderable): Promise<T>;
getMessages(): Promise<ThreadMessage[]>;
lookupUser(query: string): Promise<PlatformUser | undefined>;
postFile(args: {
bytes: Uint8Array;
filename: string;
title?: string;
altText?: string;
}): Promise<{ ok: boolean; fileId?: string; error?: string }>;
}
```
## Properties
<PropertyReference name="platform" type="string" required>
The surface this conversation lives on, e.g. `"slack"`.
</PropertyReference>
## Methods
<PropertyReference name="post" type="(ui: Renderable) => Promise<MessageRef>">
Render and post a message. JSX is rendered to the [BotNode IR](/reference/bot/types/BotNode), every event-prop handler in the tree is **bound** (minted a content-stable id, snapshotted, rewritten to the bare id), and the IR is handed to the adapter. Returns a `MessageRef` for later `update` / `delete`.
</PropertyReference>
<PropertyReference name="update" type="(ref: MessageRef, ui: Renderable) => Promise<MessageRef>">
Re-render an existing message in place — e.g. flipping a confirmation card to its approved state from a button's `onClick`.
</PropertyReference>
<PropertyReference name="delete" type="(ref: MessageRef) => Promise<void>">
Delete a posted message.
</PropertyReference>
<PropertyReference name="stream" type="(src: string | AsyncIterable<string>) => Promise<MessageRef>">
Post a placeholder and edit it in place as the source yields text. On Slack this is throttled `chat.update` with multi-message chunking — see [slack()](/reference/bot/slack).
</PropertyReference>
<PropertyReference name="runAgent" type="(input?) => Promise<MessageRef | undefined>">
Resolve the conversation's agent session, create the adapter's run renderer, and drive the run/tool/interrupt loop: streamed text is rendered into the thread, registered [tools](/reference/bot/functions/defineBotTool) are executed when the agent calls them, and captured interrupts are dispatched to `onInterrupt` handlers.
<PropertyReference name="context" type="ContextEntry[]">
Extra context entries merged on top of the bot-level `context` for this run only.
</PropertyReference>
<PropertyReference name="tools" type="BotTool[]">
Extra tools merged on top of the bot-level `tools` for this run only.
</PropertyReference>
<PropertyReference name="prompt" type="string">
A user message injected before running. Use when the input isn't already in the history the adapter reconstructs — e.g. a slash command's text, which is never posted to the channel.
</PropertyReference>
</PropertyReference>
<PropertyReference name="resume" type="(value: unknown) => Promise<MessageRef | undefined>">
Re-enter a paused interrupt run, forwarding `value` to the agent (sent as `forwardedProps.command = { resume: value }` — the LangGraph `Command` shape). Typically called from a picker button's `onClick` inside an `onInterrupt` handler.
</PropertyReference>
<PropertyReference name="awaitChoice" type="<T>(ui: Renderable) => Promise<T>">
Post a picker and **block** until an interaction in this conversation resolves it — the human-in-the-loop primitive. Resolves to the clicked control's `value`; pass `T` to type it. See the [Button](/reference/bot/components/Button) page for a full confirm-card example.
</PropertyReference>
<PropertyReference name="getMessages" type="() => Promise<ThreadMessage[]>">
Read the conversation's messages — each a `ThreadMessage` (`{ user?, text, ts?, isBot? }`). **Capability-gated**: returns `[]` when the adapter can't read history. On Slack, backed by `conversations.replies`.
</PropertyReference>
<PropertyReference name="lookupUser" type="(query: string) => Promise<PlatformUser | undefined>">
Resolve a platform user from a free-form query (name, handle, email). **Capability-gated**: returns `undefined` when unsupported.
</PropertyReference>
<PropertyReference name="postFile" type="(args: { bytes: Uint8Array; filename: string; title?: string; altText?: string }) => Promise<{ ok: boolean; fileId?: string; error?: string }>">
Upload a file into the conversation. **Capability-gated**: resolves `{ ok: false, error }` when the surface doesn't support uploads. On Slack, backed by `files.uploadV2`.
</PropertyReference>
## Usage
```tsx
bot.onMention(async ({ thread, message }) => {
// Run the agent with extra per-run context:
await thread.runAgent({
context: [
{ description: "Requesting user", value: message.user.name ?? message.user.id },
],
});
});
```
```tsx
// Inside a tool: read the thread, then block on approval.
async handler({ summary }, { thread }) {
const choice = await thread.awaitChoice<{ confirmed: boolean }>(
<ConfirmWrite action={summary} />,
);
return choice ?? { confirmed: false }; // serialized for the agent automatically
}
```
## Behavior
- **Capability gating keeps tools portable** — `getMessages` / `lookupUser` / `postFile` delegate to the adapter when supported and degrade gracefully (`[]` / `undefined` / `{ ok: false }`) when not, so the same tool runs on any surface.
- **Per-run merging** — `runAgent`'s `tools` and `context` apply to that run only, layered on top of the bot-level defaults.
- **History reconstruction** — on Slack, the conversation's `agent.messages` are rebuilt from Slack history each turn; the platform is the source of truth, so bot restarts don't lose conversations.
## Related
- [createBot](/reference/bot/functions/createBot) — where handlers receive a Thread
- [defineBotTool](/reference/bot/functions/defineBotTool) — tools receive the Thread in `ctx.thread`
- [Button](/reference/bot/components/Button) — interactive messages and `awaitChoice`
- [slack()](/reference/bot/slack) — how the Slack adapter backs these methods
@@ -0,0 +1,55 @@
---
title: "Actions"
description: "Row container for interactive controls — Buttons, Selects, and Inputs — in a bot message."
---
## Overview
`Actions` is the row container for interactive controls. Put [`Button`](/reference/bot/components/Button) and [`Select`](/reference/bot/components/Select) elements inside it. ([`Input`](/reference/bot/components/Input) is a block-level control — place it directly in the `Message`, not in an `Actions` row.)
## Import
```tsx
import { Actions } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="children" type="BotChildren">
The interactive controls in this row (`Button` / `Select`).
</PropertyReference>
## Usage
```tsx
<Message>
<Section>Ship **v1.4.2** to production?</Section>
<Actions>
<Button
style="primary"
onClick={async ({ thread }) => {
await thread.post("🚀 Shipping!");
}}
>
Ship it
</Button>
<Button
onClick={async ({ thread }) => {
await thread.post("Standing down.");
}}
>
Cancel
</Button>
</Actions>
</Message>
```
## On Slack
Renders as an `actions` block — at most **25 controls per row** (`SLACK_LIMITS.actionsElements`).
## Related
- [Button](/reference/bot/components/Button) — clickable button with inline `onClick`
- [Select](/reference/bot/components/Select) — dropdown
- [Input](/reference/bot/components/Input) — block-level text input (lives outside `Actions`)
@@ -0,0 +1,91 @@
---
title: "Button"
description: "Clickable button in a bot message — inline onClick handler, typed value, and primary/danger styling."
---
## Overview
`Button` is a clickable control for an [`Actions`](/reference/bot/components/Actions) row. Its `onClick` handler is written **inline in your JSX** and bound by the engine: the button that reaches the platform carries only an opaque action id, and the click is routed back to your handler. `Button` is generic over its `value` prop, so the value the click carries is fully typed.
## Import
```tsx
import { Button } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="onClick" type="ClickHandler<TValue>">
Inline handler run when the button is clicked. Receives an [`InteractionContext<TValue>`](/reference/bot/types/InteractionContext). Must return `void` or `Promise<void>` — for a one-liner that ends in a value-returning call, use a block body: `onClick={async ({ thread }) => { await thread.post("done"); }}`.
</PropertyReference>
<PropertyReference name="value" type="TValue">
Value echoed back on click via `ctx.action.value`, and the value [`thread.awaitChoice`](/reference/bot/classes/Thread) resolves to. Drives the `TValue` type parameter — `ctx.action.value` is inferred with no cast. Serialized into the platform payload (up to 2000 chars on Slack), so don't put secrets in it.
</PropertyReference>
<PropertyReference name="style" type='"primary" | "danger"'>
Visual accent. Omit for the default neutral style.
</PropertyReference>
<PropertyReference name="children" type="BotChildren">
The button label.
</PropertyReference>
## Usage
### Inline reply
```tsx
<Actions>
<Button
style="primary"
onClick={async ({ thread }) => {
await thread.post("🚀 Shipping!");
}}
>
Ship it
</Button>
</Actions>
```
### Typed value with awaitChoice (human-in-the-loop)
The clicked button's `value` is what [`thread.awaitChoice`](/reference/bot/classes/Thread) resolves to:
```tsx
function ConfirmWrite({ action }: { action: string }) {
return (
<Message>
<Section>{action}</Section>
<Actions>
<Button style="primary" value={{ confirmed: true }}>Create</Button>
<Button style="danger" value={{ confirmed: false }}>Cancel</Button>
</Actions>
</Message>
);
}
const choice = await thread.awaitChoice<{ confirmed: boolean }>(
<ConfirmWrite action="Create Linear issue CPK-1234?" />,
);
```
### Handlers that close over data
Inline handlers are re-derived from the component's props after a restart. If a handler closes over data that can't be reconstructed from props, wrap it with [`bind()`](/reference/bot/functions/bind).
## Behavior
- **Content-stable binding** — the handler is snapshotted under a minted opaque id (`ck:…`). Only that id and the button's `value` cross the wire to the platform; handler code and other props never leave the process.
- **Expiry** — with the default in-memory [ActionStore](/reference/bot/types/ActionStore), clicks on buttons posted before a process restart are acked but ignored (no error message is posted).
## On Slack
Renders as a `button` element: label truncated at **75 characters** (`SLACK_LIMITS.buttonText`), `action_id` capped at 255, serialized `value` capped at **2000 characters** (`SLACK_LIMITS.buttonValue`). Clicks are acked within Slack's 3-second deadline, then dispatched asynchronously.
## Related
- [InteractionContext](/reference/bot/types/InteractionContext) — what the handler receives
- [bind()](/reference/bot/functions/bind) — persist small handler args explicitly
- [ActionStore](/reference/bot/types/ActionStore) — binding, rehydration, durability
- [Thread.awaitChoice](/reference/bot/classes/Thread) — block until a click resolves
@@ -0,0 +1,38 @@
---
title: "Context"
description: "Small, muted secondary text in a bot message — footnotes and metadata."
---
## Overview
`Context` renders small, muted secondary text — footnotes, attribution, timestamps, metadata that shouldn't compete with the message body.
## Import
```tsx
import { Context } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="children" type="BotChildren">
The muted text content.
</PropertyReference>
## Usage
```tsx
<Message>
<Section>Found 3 matching runbooks.</Section>
<Context>Searched Notion · just now</Context>
</Message>
```
## On Slack
Renders as a `context` block — at most **10 elements per context block** (`SLACK_LIMITS.contextElements`).
## Related
- [Section](/reference/bot/components/Section) — primary body text
- [Divider](/reference/bot/components/Divider) — visual separation
@@ -0,0 +1,37 @@
---
title: "Divider"
description: "Horizontal rule separating blocks in a bot message."
---
## Overview
`Divider` renders a horizontal rule between blocks.
## Import
```tsx
import { Divider } from "@copilotkit/bot-ui";
```
## Props
`Divider` takes no props and no children — `<Divider />` is the entire usage. (Passing children is a compile-time error.)
## Usage
```tsx
<Message>
<Section>Open issues</Section>
<Divider />
<Section>Closed issues</Section>
</Message>
```
## On Slack
Renders as a `divider` block.
## Related
- [Message](/reference/bot/components/Message) — the containing message
- [Context](/reference/bot/components/Context) — muted secondary text
@@ -0,0 +1,51 @@
---
title: "Fields"
description: "Two-column grid of label/value cells in a bot message, built from Field children."
---
## Overview
`Fields` lays out a compact two-column grid of label/value cells — status, assignee, priority, dates. Each cell is a `Field` child.
## Import
```tsx
import { Fields, Field } from "@copilotkit/bot-ui";
```
## Props
### Fields
<PropertyReference name="children" type="BotChildren">
The `Field` cells to lay out.
</PropertyReference>
### Field
<PropertyReference name="children" type="BotChildren">
One cell's content. Use markdown for the label/value pattern, e.g. `**Status**\nIn Progress` (double asterisks — single `*…*` is GFM italic and renders as italics on Slack).
</PropertyReference>
## Usage
```tsx
<Message>
<Header>CPK-1234</Header>
<Fields>
<Field>**Status**: In Progress</Field>
<Field>**Priority**: High</Field>
<Field>**Assignee**: Ada</Field>
<Field>**Updated**: today</Field>
</Fields>
</Message>
```
## On Slack
Renders as a `section` block's `fields` array — at most **10 fields per section** (`SLACK_LIMITS.fieldsPerSection`), each truncated at **2000 characters** (`SLACK_LIMITS.fieldText`). Slack displays them two per row.
## Related
- [Section](/reference/bot/components/Section) — single-column body text
- [Table](/reference/bot/components/Table) — full tabular data
@@ -0,0 +1,37 @@
---
title: "Header"
description: "Bold header / title row for a bot message."
---
## Overview
`Header` renders a bold title row — typically the first child of a [`Message`](/reference/bot/components/Message).
## Import
```tsx
import { Header } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="children" type="BotChildren">
The header text. Plain text only — markdown is not interpreted in headers.
</PropertyReference>
## Usage
```tsx
<Message>
<Header>Deploy v1.4.2</Header>
</Message>
```
## On Slack
Renders as a `header` block, truncated at **150 characters** (`SLACK_LIMITS.headerText`).
## Related
- [Message](/reference/bot/components/Message) — the containing message
- [Section](/reference/bot/components/Section) — markdown body text
@@ -0,0 +1,41 @@
---
title: "Image"
description: "Image block in a bot message."
---
## Overview
`Image` embeds an image in the message by URL.
## Import
```tsx
import { Image } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="url" type="string" required>
The image URL. Must be reachable by the platform (Slack fetches it server-side).
</PropertyReference>
<PropertyReference name="alt" type="string">
Alternative text for accessibility.
</PropertyReference>
## Usage
```tsx
<Message>
<Section>Incident timeline:</Section>
<Image url="https://example.com/chart.png" alt="Error rate over time" />
</Message>
```
## On Slack
Renders as an `image` block. To post a locally generated image (e.g. a rendered chart), upload it with [`thread.postFile`](/reference/bot/classes/Thread) instead — `Image` is for URLs.
## Related
- [Thread.postFile](/reference/bot/classes/Thread) — upload image bytes into the thread
@@ -0,0 +1,56 @@
---
title: "Input"
description: "Text input control in a bot message with an inline onSubmit handler."
---
## Overview
`Input` is a free-text control. The submitted text arrives in the inline `onSubmit` handler as a `string`. Place it as a **direct child of [`Message`](/reference/bot/components/Message)** (a sibling of `Section` / `Actions`) — on Slack it renders as its own input block, and an `Input` placed inside an [`Actions`](/reference/bot/components/Actions) row is dropped by the renderer.
## Import
```tsx
import { Input } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="onSubmit" type="ClickHandler<string>">
Inline handler run on submit. `ctx.action.value` is the entered text. See [`InteractionContext`](/reference/bot/types/InteractionContext). Must return `void` or `Promise<void>`.
</PropertyReference>
<PropertyReference name="placeholder" type="string">
Placeholder text shown while empty.
</PropertyReference>
<PropertyReference name="multiline" type="boolean">
Render as a multi-line text area.
</PropertyReference>
<PropertyReference name="name" type="string">
Identifier for the input. Currently unused by the Slack adapter.
</PropertyReference>
## Usage
```tsx
<Message>
<Section>Anything to add before I file this?</Section>
<Input
placeholder="Add a note for the postmortem…"
multiline
onSubmit={async ({ thread, action }) => {
await thread.post(`Noted: ${action.value}`);
}}
/>
</Message>
```
## On Slack
Renders as an `input` block wrapping a `plain_text_input` element. Must be a top-level block — inside an `Actions` row the Slack renderer silently drops it.
## Related
- [Button](/reference/bot/components/Button) — single-click control
- [InteractionContext](/reference/bot/types/InteractionContext) — what the handler receives
@@ -0,0 +1,37 @@
---
title: "Markdown"
description: "Explicit markdown text block in a bot message."
---
## Overview
`Markdown` is an explicit markdown text block. It behaves like [`Section`](/reference/bot/components/Section) but signals intent: this content *is* markdown (e.g. agent output you're passing through), not incidental body text.
## Import
```tsx
import { Markdown } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="children" type="BotChildren">
The markdown source.
</PropertyReference>
## Usage
```tsx
<Message>
<Markdown>{agentSummary}</Markdown>
</Message>
```
## On Slack
Renders as a `section` block whose text is passed through the adapter's Markdown → mrkdwn translation, truncated at **3000 characters** (`SLACK_LIMITS.sectionText`).
## Related
- [Section](/reference/bot/components/Section) — general body text
- [markdownToMrkdwn](/reference/bot/slack/markdownToMrkdwn) — the Markdown → mrkdwn translation
@@ -0,0 +1,50 @@
---
title: "Message"
description: "Root container for a single posted bot message, with an optional accent color rail."
---
## Overview
`Message` is the root container for one posted message. Every card you post with [`thread.post`](/reference/bot/classes/Thread) starts with a `Message` wrapping the blocks that make it up — headers, sections, fields, action rows.
## Import
```tsx
import { Message } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="accent" type="string">
Accent color (hex, e.g. `"#27AE60"`) for the message's colored rail. On Slack, Block Kit has no native accent, so an accented message is posted as an attachment with a colored left bar (`attachments: [{ color, blocks }]`).
</PropertyReference>
<PropertyReference name="children" type="BotChildren">
The message's blocks: [`Header`](/reference/bot/components/Header), [`Section`](/reference/bot/components/Section), [`Fields`](/reference/bot/components/Fields), [`Context`](/reference/bot/components/Context), [`Actions`](/reference/bot/components/Actions), [`Image`](/reference/bot/components/Image), [`Divider`](/reference/bot/components/Divider), [`Table`](/reference/bot/components/Table).
</PropertyReference>
## Usage
```tsx
import { Message, Header, Section, Context } from "@copilotkit/bot-ui";
function IssueCard({ id, title }: { id: string; title: string }) {
return (
<Message accent="#5865F2">
<Header>{id}</Header>
<Section>{title}</Section>
<Context>Updated just now</Context>
</Message>
);
}
```
## On Slack
Renders as the message's `blocks` array — capped at **50 blocks per message** (`SLACK_LIMITS.blocksPerMessage`); the renderer clamps overflow instead of failing. With `accent`, the blocks move into a colored attachment.
## Related
- [Thread.post](/reference/bot/classes/Thread) — posting a rendered message
- [renderToIR](/reference/bot/functions/renderToIR) — how JSX becomes the BotNode IR
- [renderBlockKit](/reference/bot/slack/renderBlockKit) — Block Kit translation and budgets
@@ -0,0 +1,38 @@
---
title: "Section"
description: "A block of markdown body text in a bot message."
---
## Overview
`Section` is the workhorse text block: a paragraph of body text with markdown support (`**bold**`, `` `code` ``, links, …).
## Import
```tsx
import { Section } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="children" type="BotChildren">
The body text. Markdown in string children is rendered by the platform adapter (on Slack, translated to mrkdwn).
</PropertyReference>
## Usage
```tsx
<Message>
<Section>Ship **v1.4.2** to production? See the `CHANGELOG` first.</Section>
</Message>
```
## On Slack
Renders as a `section` block with mrkdwn text, truncated at **3000 characters** (`SLACK_LIMITS.sectionText`). Markdown is translated to mrkdwn (e.g. `**bold**` → `*bold*`) by the adapter.
## Related
- [Markdown](/reference/bot/components/Markdown) — an explicit markdown block
- [Fields](/reference/bot/components/Fields) — two-column label/value layout
- [markdownToMrkdwn](/reference/bot/slack/markdownToMrkdwn) — the Markdown → mrkdwn translation
@@ -0,0 +1,55 @@
---
title: "Select"
description: "Dropdown control in a bot message with an inline onSelect handler."
---
## Overview
`Select` is a dropdown for an [`Actions`](/reference/bot/components/Actions) row. The selection handler is inline, like [`Button`](/reference/bot/components/Button)'s `onClick`; the selected option's `value` arrives as a `string`.
## Import
```tsx
import { Select } from "@copilotkit/bot-ui";
```
## Props
<PropertyReference name="onSelect" type="ClickHandler<string>">
Inline handler run on selection. `ctx.action.value` is the chosen option's `value` (a `string`). See [`InteractionContext`](/reference/bot/types/InteractionContext). Must return `void` or `Promise<void>`.
</PropertyReference>
<PropertyReference name="placeholder" type="string">
Placeholder shown before a selection is made.
</PropertyReference>
<PropertyReference name="options" type="{ label: string; value: string }[]" required>
The selectable options (`SelectOption[]`).
</PropertyReference>
## Usage
```tsx
<Actions>
<Select
placeholder="Pick a severity"
options={[
{ label: "SEV1 — page someone", value: "sev1" },
{ label: "SEV2 — business hours", value: "sev2" },
{ label: "SEV3 — backlog", value: "sev3" },
]}
onSelect={async ({ thread, action }) => {
await thread.post(`Severity set to ${action.value}.`);
}}
/>
</Actions>
```
## On Slack
Renders as a `static_select` element — at most **100 options** (`SLACK_LIMITS.selectOptions`).
## Related
- [Button](/reference/bot/components/Button) — single-click control
- [InteractionContext](/reference/bot/types/InteractionContext) — what the handler receives
@@ -0,0 +1,73 @@
---
title: "Table"
description: "Data table in a bot message, built from Row and Cell children with optional column alignment."
---
## Overview
`Table` renders tabular data. Columns are declared on the table; data flows in as `Row` children containing `Cell` children.
## Import
```tsx
import { Table, Row, Cell } from "@copilotkit/bot-ui";
```
## Props
### Table
<PropertyReference name="columns" type='{ header: string; align?: "left" | "center" | "right" }[]'>
Column definitions (`TableColumn[]`): a header label and an optional alignment per column. Use `align: "right"` for numeric columns.
</PropertyReference>
<PropertyReference name="children" type="BotChildren">
The `Row` elements.
</PropertyReference>
### Row
<PropertyReference name="children" type="BotChildren">
The row's `Cell` elements, in column order.
</PropertyReference>
### Cell
<PropertyReference name="children" type="BotChildren">
One cell's content.
</PropertyReference>
## Usage
```tsx
<Message>
<Header>Incidents this week</Header>
<Table
columns={[
{ header: "Issue" },
{ header: "Severity" },
{ header: "Count", align: "right" },
]}
>
<Row>
<Cell>CPK-1201</Cell>
<Cell>SEV2</Cell>
<Cell>14</Cell>
</Row>
<Row>
<Cell>CPK-1188</Cell>
<Cell>SEV3</Cell>
<Cell>3</Cell>
</Row>
</Table>
</Message>
```
## On Slack
Renders as a native `table` block — at most **20 columns**, **100 data rows** (the header row built from `columns` is separate), and **2000 characters per cell** (`SLACK_LIMITS.tableColumns` / `tableRows` / `cellText`); overflow is clamped.
## Related
- [Fields](/reference/bot/components/Fields) — lightweight two-column label/value layout
- [renderBlockKit](/reference/bot/slack/renderBlockKit) — Block Kit mapping and budgets
@@ -0,0 +1,51 @@
---
title: "bind"
description: "Attach a small persisted payload to an inline interaction handler so it survives cold-path rehydration."
---
## Overview
Inline handlers (`onClick` and friends) are bound **by content** — component identity + path + serializable props — so the engine can re-derive a handler after a restart by re-rendering the component. `bind` attaches a small `args` payload to a handler: on dispatch, the handler receives `args` via `ctx.action.value`, and the payload is snapshotted alongside the minted action id in the [ActionStore](/reference/bot/types/ActionStore).
## Signature
```ts
import { bind } from "@copilotkit/bot-ui";
function bind(handler: ClickHandler, args: unknown): ClickHandler;
```
## Parameters
<PropertyReference name="handler" type="ClickHandler" required>
The handler to run on dispatch. Receives the normal [`InteractionContext`](/reference/bot/types/InteractionContext), with `ctx.action.value` set to `args`.
</PropertyReference>
<PropertyReference name="args" type="unknown" required>
The payload handed back to the handler as `ctx.action.value`. Keep it **small** — it's snapshotted with the action (see the v1 caveat under Behavior).
</PropertyReference>
## Usage
```tsx
import { bind } from "@copilotkit/bot-ui";
import type { ClickHandler } from "@copilotkit/bot-ui";
const handleChoice: ClickHandler = async ({ thread, action }) => {
await thread.post(`You chose ${JSON.stringify(action.value)}.`);
};
<Button onClick={bind(handleChoice, { choiceId: "abc123" })}>Choose</Button>;
```
## Behavior
- The returned handler is tagged; when the engine binds the tree, it stores `args` in the action snapshot (`boundArgs`), and the hot-path dispatch hands `args` back via `ctx.action.value`.
- **v1 caveat:** the cold path (cache miss → snapshot rehydration) re-renders the component from its frozen props and uses the *re-created* handler — the persisted `boundArgs` is **not yet injected** on rehydration. In practice that means `args` must currently be derivable from the component's props to survive a restart; treat restart-proof bind args as not-yet-supported.
- Prefer plain inline handlers when the data is already in the component's props — `bind` is for handler-specific payloads you don't want to thread through props.
## Related
- [Button](/reference/bot/components/Button) — where inline handlers live
- [ActionStore](/reference/bot/types/ActionStore) — snapshots, rehydration, durability
- [InteractionContext](/reference/bot/types/InteractionContext) — what handlers receive
@@ -0,0 +1,126 @@
---
title: "createBot"
description: "Create a bot: wire platform adapters to an AG-UI agent, register tools, context, and slash commands, and get the handler surface."
---
## Overview
`createBot` is the entry point of `@copilotkit/bot`. It wires one or more platform adapters to an AG-UI agent and returns a `Bot` — the surface for registering turn handlers, interaction handlers, interrupt handlers, slash commands, and tools, plus `start()` / `stop()` lifecycle control.
For a complete walkthrough, see the [Slack quickstart](/slack).
## Signature
```ts
import { createBot } from "@copilotkit/bot";
function createBot(opts: CreateBotOptions): Bot;
```
## Parameters
<PropertyReference name="opts" type="CreateBotOptions" required>
Bot configuration.
<PropertyReference name="adapters" type="PlatformAdapter[]" required>
The platform adapters to run, e.g. [`slack(...)`](/reference/bot/slack). `start()` brings every adapter up; `stop()` brings them down.
</PropertyReference>
<PropertyReference name="agent" type="AbstractAgent | ((threadId: string) => AbstractAgent)">
The AG-UI agent behind [`thread.runAgent()`](/reference/bot/classes/Thread) — a single instance or a per-conversation factory receiving the conversation's thread id. Optional: when omitted, calling `thread.runAgent()` throws.
</PropertyReference>
<PropertyReference name="actionStore" type="ActionStore" default="new InMemoryActionStore()">
Where snapshots of inline JSX handlers are persisted. The in-memory default loses actions on restart — see [ActionStore](/reference/bot/types/ActionStore) for the contract and durability tiers.
</PropertyReference>
<PropertyReference name="tools" type="BotTool[]">
Tools forwarded to the agent as frontend tools on every run. See [defineBotTool](/reference/bot/functions/defineBotTool).
</PropertyReference>
<PropertyReference name="context" type="ContextEntry[]">
Knowledge folded into the agent's context on each run. A `ContextEntry` is `{ description: string; value: string }`.
</PropertyReference>
<PropertyReference name="commands" type="BotCommand[]">
Slash commands to register up front — equivalent to calling `bot.onCommand` per command. See [defineBotCommand](/reference/bot/functions/defineBotCommand).
</PropertyReference>
</PropertyReference>
## Return Value
<PropertyReference name="Bot" type="Bot">
The bot's registration and lifecycle surface.
<PropertyReference name="onMention" type="(h: BotHandler) => void">
Register a turn handler. `BotHandler` receives `{ thread, message }` — a [`Thread`](/reference/bot/classes/Thread) and the incoming message (`{ text, user, ref, platform }`). On Slack one mention handler covers @-mentions, replies in threads the bot owns, and DMs.
</PropertyReference>
<PropertyReference name="onMessage" type="(h: BotHandler) => void">
Register a turn handler that fires only when **no** mention handler is registered — routing is mention-preferred (see Behavior).
</PropertyReference>
<PropertyReference name="onInteraction" type="<TValue>(id: string, h: (ctx: InteractionContext<TValue>) => void | Promise<void>) => void">
Escape-hatch handler for a known action `id`, bypassing the action registry. `ctx.action.value` is typed as `TValue`. Most bots don't need this — inline `onClick` handlers on [`Button`](/reference/bot/components/Button) are bound automatically.
</PropertyReference>
<PropertyReference name="onInterrupt" type="<TPayload>(eventName: string, h: (args: { payload: TPayload; thread: Thread }) => void | Promise<void>) => void">
Handle a captured agent interrupt (a LangGraph-style `on_interrupt` custom event). The handler typically posts a picker and resumes the run with [`thread.resume(value)`](/reference/bot/classes/Thread) when the user answers.
</PropertyReference>
<PropertyReference name="onCommand" type="(command: BotCommand) => void">
Register a slash command — a full [`BotCommand`](/reference/bot/functions/defineBotCommand), or the shorthand overload `onCommand(name, handler)` for a free-text command. Commands delivered by the platform but not registered here are ignored.
</PropertyReference>
<PropertyReference name="tool" type="(t: BotTool) => void">
Register a tool (alternative to `opts.tools`). Must be called before `start()` — the tool descriptors handed to the agent are computed at startup.
</PropertyReference>
<PropertyReference name="start" type="() => Promise<void>">
Bring all adapters up. Also forwards declared commands to adapters that implement `registerCommands` (e.g. a Discord-style application-command API); adapters without it — including Slack — are skipped.
</PropertyReference>
<PropertyReference name="stop" type="() => Promise<void>">
Bring all adapters down.
</PropertyReference>
</PropertyReference>
## Usage
```tsx
import { createBot } from "@copilotkit/bot";
import { slack, defaultSlackTools, defaultSlackContext } from "@copilotkit/bot-slack";
const bot = createBot({
adapters: [
slack({
botToken: process.env.SLACK_BOT_TOKEN!,
appToken: process.env.SLACK_APP_TOKEN!,
}),
],
agent: (threadId) => makeAgent(threadId),
tools: [...defaultSlackTools, ...appTools],
context: [...defaultSlackContext, ...appContext],
});
bot.onMention(async ({ thread }) => {
await thread.runAgent();
});
await bot.start();
```
## Behavior
- **Mention-preferred routing** — there is no per-turn "kind": when any `onMention` handler is registered, all turns route to the mention handlers; otherwise `onMessage` handlers fire. Registering identical handlers on both never double-fires.
- **Expired actions are swallowed** — a click whose snapshot is gone (e.g. after a restart with the in-memory store) raises `ActionExpiredError` internally; `createBot` swallows it, so the click is acked but ignored and no message is posted.
- **Commands are matched case-insensitively** and without the leading slash.
- **No agent, no `runAgent`** — omitting `agent` is fine for bots that only post UI, but `thread.runAgent()` will throw.
## Related
- [Thread](/reference/bot/classes/Thread) — the per-conversation handle your handlers receive
- [defineBotTool](/reference/bot/functions/defineBotTool) — tools the agent can call
- [defineBotCommand](/reference/bot/functions/defineBotCommand) — slash commands
- [ActionStore](/reference/bot/types/ActionStore) — action binding and durability
- [Slack quickstart](/slack) — zero to a working bot
@@ -0,0 +1,96 @@
---
title: "defineBotCommand"
description: "Define a typed slash command — name, optional Standard Schema options, and a handler receiving CommandContext."
---
## Overview
`defineBotCommand` defines a slash command with full type inference: `ctx.options` is inferred from the `options` schema. Commands are registered via `createBot({ commands })` or `bot.onCommand`, and routed to the matching handler when the platform delivers an invocation.
## Signature
```ts
import { defineBotCommand } from "@copilotkit/bot";
function defineBotCommand<Schema extends ObjectSchema>(
command: BotCommand<Schema>,
): BotCommand<Schema>;
```
## Parameters
<PropertyReference name="command" type="BotCommand<Schema>" required>
The command definition.
<PropertyReference name="name" type="string" required>
Command name without the leading slash (e.g. `"triage"`). Matched case-insensitively.
</PropertyReference>
<PropertyReference name="description" type="string">
Help text; also the registration label on surfaces that register commands up front (e.g. a Discord-style application-command API).
</PropertyReference>
<PropertyReference name="options" type="ObjectSchema">
Optional Standard Schema for typed options. Used natively on surfaces that deliver structured args; on text-only surfaces (Slack) it's unused — read `ctx.text`.
</PropertyReference>
<PropertyReference name="handler" type="(ctx: CommandContext) => void | Promise<void>" required>
The command handler.
</PropertyReference>
</PropertyReference>
### CommandContext
<PropertyReference name="thread" type="Thread" required>
The conversation the command was invoked in.
</PropertyReference>
<PropertyReference name="command" type="string" required>
The invoked command name, normalized (no leading slash, lower-cased).
</PropertyReference>
<PropertyReference name="text" type="string" required>
Raw argument string after the command name — the form Slack delivers.
</PropertyReference>
<PropertyReference name="options" type="TOptions" required>
Parsed, typed options. Populated on surfaces with native structured args; empty on text-only surfaces.
</PropertyReference>
<PropertyReference name="user" type="PlatformUser">
The invoking user, when the surface provides it.
</PropertyReference>
<PropertyReference name="platform" type="string" required>
The active surface, e.g. `"slack"`.
</PropertyReference>
## Usage
```tsx
import { defineBotCommand } from "@copilotkit/bot";
import { z } from "zod";
const triage = defineBotCommand({
name: "triage",
description: "Summarize and file the current thread.",
options: z.object({ priority: z.enum(["low", "high"]).optional() }),
async handler({ thread, text }) {
await thread.runAgent({ prompt: `Triage: ${text}` });
},
});
```
A slash command's text is never posted to the channel, so it isn't in the history the adapter reconstructs — pass it to the agent explicitly with [`runAgent({ prompt })`](/reference/bot/classes/Thread).
## Behavior
- **Declare commands with the platform** — platforms only deliver commands they know about. On Slack, every command must **also** be declared in the app configuration (the manifest's `slash_commands` section, or "Slash Commands" in the app settings); an undeclared command is silently dropped, even over Socket Mode. See the [Slack quickstart](/slack).
- **Matching is case-insensitive** and ignores the leading slash; commands delivered by the platform but not registered on the bot are ignored.
- **Up-front registration** — on `start()`, declared commands are forwarded to adapters that implement `registerCommands` (e.g. a Discord-style application-command API); adapters without it, including Slack, are skipped.
## Related
- [createBot](/reference/bot/functions/createBot) — registering commands (`commands` option / `onCommand`)
- [Thread](/reference/bot/classes/Thread) — `runAgent({ prompt })`
- [defineBotTool](/reference/bot/functions/defineBotTool) — the tool analog
@@ -0,0 +1,126 @@
---
title: "defineBotTool"
description: "Define a typed BotTool — a frontend tool the agent can call whose handler runs in the bot with a Thread in scope."
---
## Overview
`defineBotTool` defines a `BotTool` with full type inference: the handler's `args` are inferred from the `parameters` schema. A `BotTool` is forwarded to the agent as a **frontend tool**; when the agent calls it, the handler runs **in the bot**, with the conversation's [`Thread`](/reference/bot/classes/Thread) in scope — so a tool can read the thread, post JSX cards, or block on a human choice.
## Signature
```ts
import { defineBotTool } from "@copilotkit/bot";
function defineBotTool<Schema extends ObjectSchema>(
tool: BotTool<Schema>,
): BotTool<Schema>;
```
## Parameters
<PropertyReference name="tool" type="BotTool<Schema>" required>
The tool definition.
<PropertyReference name="name" type="string" required>
Tool name the agent calls, e.g. `"read_thread"`.
</PropertyReference>
<PropertyReference name="description" type="string" required>
What the tool does — written for the model. This is the main lever for when the agent reaches for the tool.
</PropertyReference>
<PropertyReference name="parameters" type="ObjectSchema" required>
Any Standard Schema object schema (Zod, Valibot, ArkType, …). Converted to JSON Schema for the LLM, and used to validate the args on the way back in.
</PropertyReference>
<PropertyReference name="handler" type="(args, ctx: BotToolContext) => Promise<unknown> | unknown" required>
Runs in the bot when the agent calls the tool. `args` is inferred from `parameters`. The return value is what the **agent (LLM) reads back** as the tool result: a `string` is sent as-is, `null`/`undefined` becomes an empty string, anything else is JSON-stringified automatically. Return something meaningful — the data itself for a data tool, a short confirmation (e.g. `"Displayed the issue card."`) for a tool that posts UI, or the actual error text on failure so the model can repair and retry.
</PropertyReference>
</PropertyReference>
### BotToolContext
The single shared context every handler receives — there are no per-adapter generics:
<PropertyReference name="thread" type="Thread" required>
The conversation the tool call belongs to. Platform power is reached only through capability-gated [`Thread`](/reference/bot/classes/Thread) methods (`getMessages`, `lookupUser`, `postFile`, `post`, …), which keeps tools portable across surfaces.
</PropertyReference>
<PropertyReference name="message" type="IncomingMessage">
The triggering message, when the adapter supplies it.
</PropertyReference>
<PropertyReference name="user" type="PlatformUser">
The requesting user, when the adapter supplies it.
</PropertyReference>
<PropertyReference name="signal" type="AbortSignal">
Abort signal for long-running handlers, when the adapter supplies one.
</PropertyReference>
<PropertyReference name="platform" type="string" required>
The active surface, e.g. `"slack"`.
</PropertyReference>
## Usage
### A data tool
```tsx
import { defineBotTool } from "@copilotkit/bot";
import { z } from "zod";
const readThread = defineBotTool({
name: "read_thread",
description: "Read the messages in the current conversation.",
parameters: z.object({}),
async handler(_args, { thread }) {
return await thread.getMessages();
},
});
```
### A render tool (posts JSX)
A common pattern: the component's prop schema doubles as the tool's input schema, and the agent "renders" by calling the tool.
```tsx
const issueCard = defineBotTool({
name: "issue_card",
description: "Render one issue as a rich card.",
parameters: issueCardSchema,
async handler(props, { thread }) {
await thread.post(<IssueCard {...props} />);
return "Displayed the issue card to the user.";
},
});
```
### A blocking human-in-the-loop tool
```tsx
const confirmWrite = defineBotTool({
name: "confirm_write",
description: "Ask the user to approve a write before performing it.",
parameters: z.object({ action: z.string() }),
async handler({ action }, { thread }) {
const choice = await thread.awaitChoice<{ confirmed: boolean }>(
<ConfirmWrite action={action} />,
);
return choice ?? { confirmed: false }; // serialized for the agent automatically
},
});
```
## Behavior
- **Validation** — args coming back from the model are validated against `parameters`; invalid args don't reach your handler.
- **Registration** — pass tools via `createBot({ tools })` or `bot.tool(t)`; per-run extras go through [`thread.runAgent({ tools })`](/reference/bot/classes/Thread). Tools must be registered before `start()`.
- **Portability** — handlers receive the same `BotToolContext` on every platform; a tool written against `thread` methods runs unchanged on any adapter that supports them.
## Related
- [Thread](/reference/bot/classes/Thread) — the handle in `ctx.thread`
- [createBot](/reference/bot/functions/createBot) — registering tools
- [defineBotCommand](/reference/bot/functions/defineBotCommand) — the command analog
@@ -0,0 +1,58 @@
---
title: "renderToIR"
description: "Render bot JSX to the serializable BotNode IR that platform adapters translate to native payloads."
---
## Overview
`renderToIR` is the bridge from JSX to data: it recursively invokes component functions until only intrinsic string-typed nodes remain, producing the serializable [`BotNode[]`](/reference/bot/types/BotNode) IR an adapter translates into its native format. You rarely call it directly — [`thread.post`](/reference/bot/classes/Thread) renders for you — but it's the core primitive for testing components or building custom adapters.
## Signature
```ts
import { renderToIR } from "@copilotkit/bot-ui";
function renderToIR(ui: Renderable): BotNode[];
```
## Parameters
<PropertyReference name="ui" type="Renderable" required>
What to render: a JSX element (`BotNode`), an array of them, a plain `string`, or the `{ raw }` escape hatch (see Behavior).
</PropertyReference>
## Return Value
<PropertyReference name="nodes" type="BotNode[]">
The flattened IR: every node has an intrinsic string `type` and serializable `props`. See [BotNode](/reference/bot/types/BotNode).
</PropertyReference>
## Usage
```tsx
import { Message, Header, renderToIR } from "@copilotkit/bot-ui";
function Greeting({ name }: { name: string }) {
return (
<Message>
<Header>Hello {name}</Header>
</Message>
);
}
const ir = renderToIR(<Greeting name="Ada" />);
// [{ type: "message", props: { children: [{ type: "header", props: { … } }] } }]
```
## Behavior
- **Component functions are invoked** with their props until the tree is intrinsic-only; `Fragment` flattens its children.
- **Strings and numbers** in children become `{ type: "text", props: { value } }`; `false` / `null` / `undefined` render nothing (so `{cond && <Section>…</Section>}` works).
- **`{ raw }` short-circuits**: `renderToIR({ raw: payload })` passes through as `{ type: "raw", props: { value: payload } }`, for handing an adapter a native payload (e.g. hand-built Block Kit) directly.
- **Purity is required** — components must be pure functions of serializable props: same props in, same tree out. This is what makes content-stable action binding and cold-path re-render rehydration possible (see [ActionStore](/reference/bot/types/ActionStore)).
## Related
- [BotNode](/reference/bot/types/BotNode) — the IR shape
- [Message](/reference/bot/components/Message) — the component vocabulary
- [ActionStore](/reference/bot/types/ActionStore) — why purity matters
@@ -0,0 +1,73 @@
---
title: "Bots"
description: "API reference for the CopilotKit bot stack — @copilotkit/bot, @copilotkit/bot-ui, and @copilotkit/bot-slack."
---
The bot stack turns any [AG-UI](https://docs.ag-ui.com) agent into a chat-platform bot. Three packages, three jobs:
| Package | Role |
|---------|------|
| `@copilotkit/bot` | The platform-agnostic engine: [`createBot`](/reference/bot/functions/createBot), handler registration, the agent run/tool/interrupt loop, and action binding. Re-exports the `@copilotkit/bot-ui` vocabulary. |
| `@copilotkit/bot-ui` | A pure JSX runtime + cross-platform [component vocabulary](/reference/bot/components/Message) for rich messages — no React. |
| `@copilotkit/bot-slack` | The Slack adapter: Socket Mode ingress, JSX → Block Kit via [`slack()`](/reference/bot/slack), `chat.update` streaming. |
```sh
pnpm add @copilotkit/bot @copilotkit/bot-ui @copilotkit/bot-slack
```
<Callout type="info">
New to the stack? Start with the [Slack quickstart](/slack) — zero to a working bot, then come back here for the API surface.
</Callout>
## Setup
The packages ship as **ES modules only** (`"type": "module"` required). To author messages as JSX, point the TypeScript JSX factory at `@copilotkit/bot-ui` and write `.tsx` files:
```json title="tsconfig.json"
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@copilotkit/bot-ui"
}
}
```
The package ships its own `JSX` namespace via `@copilotkit/bot-ui/jsx-runtime`, so JSX is statically type-checked: unknown attributes, wrong prop values, and bad children are compile-time errors.
## Start here
<Cards>
<Card
title="createBot()"
description="Create the bot: adapters, agent, tools, context, commands, and the handler surface."
href="/reference/bot/functions/createBot"
icon={<LinkIcon />}
/>
<Card
title="Thread"
description="The per-conversation handle: post, stream, runAgent, awaitChoice, and capability-gated platform methods."
href="/reference/bot/classes/Thread"
icon={<LinkIcon />}
/>
<Card
title="Components"
description="The JSX vocabulary for rich messages: Message, Header, Section, Button, Select, Table, and friends."
href="/reference/bot/components/Message"
icon={<LinkIcon />}
/>
<Card
title="slack()"
description="The Slack adapter: options, Block Kit budgets, streaming, and mrkdwn translation."
href="/reference/bot/slack"
icon={<LinkIcon />}
/>
</Cards>
## How the pieces fit
A turn flows through three stages. The adapter normalizes a platform event into a turn and hands it to your handler, which calls [`thread.runAgent()`](/reference/bot/classes/Thread). The engine drives the run loop — streaming text into the thread, executing [tools](/reference/bot/functions/defineBotTool) when the agent calls them, capturing interrupts. Anything you post is JSX lowered to the [BotNode](/reference/bot/types/BotNode) intermediate representation (plain serializable data), which the adapter translates to the platform's native format — with interactive handlers bound through the [ActionStore](/reference/bot/types/ActionStore).
## Related
- [Slack quickstart](/slack) — create the Slack app, install, and run your first bot
- [On-call triage example](https://github.com/CopilotKit/CopilotKit/tree/main/examples/slack) — a full bot over Linear + Notion MCP with HITL and slash commands
@@ -0,0 +1,49 @@
---
title: "SanitizingHttpAgent"
description: "An HttpAgent that tolerates the AG-UI event streams real agent backends emit — use it to connect the bot to a remote runtime over AGENT_URL."
---
## Overview
`SanitizingHttpAgent` is an `HttpAgent` (from `@ag-ui/client`) that tolerates the event streams real agent backends emit. The stock `HttpAgent` re-validates every streamed event against a strict schema, and a single rejected event aborts the entire run — some backends (notably LangGraph) legitimately emit events that fail it, e.g. a `TOOL_CALL_START` with a `null` `parentMessageId`, which is exactly the shape that carries interrupts. This class parses the same SSE stream but coerces the known nullable-string fields instead, so interrupts and human-in-the-loop work over HTTP.
This is the agent class to use when the bot connects to a **remote AG-UI backend** — the bot-process/runtime-process split shown in the [Slack quickstart](/slack) and used by the [triage example](https://github.com/CopilotKit/CopilotKit/tree/main/examples/slack).
## Signature
```ts
import { SanitizingHttpAgent } from "@copilotkit/bot-slack";
class SanitizingHttpAgent extends HttpAgent {
constructor(config: HttpAgentConfig); // { url, headers?, … }
}
```
## Usage
```ts
const bot = createBot({
adapters: [slack({ botToken, appToken })],
agent: (threadId) => {
const agent = new SanitizingHttpAgent({
url: process.env.AGENT_URL!, // e.g. http://localhost:8200/api/copilotkit/agent/assistant/run
headers: process.env.AGENT_AUTH_HEADER
? { Authorization: process.env.AGENT_AUTH_HEADER }
: undefined,
});
agent.threadId = threadId;
return agent;
},
});
```
## Behavior
- **Trusted-runtime tradeoff** — the coercion replaces the stock transform's *entire* strict re-validation step, not just the offending field. That's acceptable only because the bot talks to a runtime you control; transport and HTTP errors still throw.
- **Visible in production** — the first coercion logs a one-time breadcrumb so the workaround's use is observable.
## Related
- [createBot](/reference/bot/functions/createBot) — the `agent` factory option
- [slack()](/reference/bot/slack) — the adapter this ships with
- [Slack quickstart](/slack) — the split-process setup this enables
@@ -0,0 +1,33 @@
---
title: "defaultSlackContext"
description: "The Slack context entries the adapter ships — tagging procedure, Markdown-vs-mrkdwn guidance, and the thread/DM conversation model."
---
## Overview
`defaultSlackContext` is the list of Slack-specific [`ContextEntry`](/reference/bot/functions/createBot) values the package ships, meant to be spread into [`createBot({ context })`](/reference/bot/functions/createBot). It is **not auto-applied** — you spread it explicitly. Each entry is also exported individually:
| Entry | Teaches the agent |
|-------|-------------------|
| `slackTaggingContext` | How to @-mention people: resolve via the [`lookup_slack_user`](/reference/bot/slack/defaultSlackTools) tool and paste the returned `<@U…>` mention verbatim |
| `slackFormattingContext` | What formatting survives in Slack — Markdown is translated to mrkdwn for it (see [markdownToMrkdwn](/reference/bot/slack/markdownToMrkdwn)) |
| `slackConversationModelContext` | The Slack conversation model — channels, threads, and DMs, and how the bot participates in each |
## Usage
```ts
import { defaultSlackContext } from "@copilotkit/bot-slack";
const bot = createBot({
// …
context: [...defaultSlackContext, ...myAppContext],
});
```
Context entries are folded into the agent's system context on every [`thread.runAgent()`](/reference/bot/classes/Thread); per-run additions go through `runAgent({ context })`.
## Related
- [defaultSlackTools](/reference/bot/slack/defaultSlackTools) — the companion tool the tagging procedure relies on
- [createBot](/reference/bot/functions/createBot) — the `context` option
- [slack()](/reference/bot/slack) — the adapter
@@ -0,0 +1,39 @@
---
title: "defaultSlackTools"
description: "The Slack tools the adapter ships — lookup_slack_user resolves a person to a <@USERID> mention."
---
## Overview
`defaultSlackTools` is the flat list of universal Slack tools the package ships. It is **not auto-applied** — you spread it explicitly, so there's no hidden behavior. Today it contains one tool, also exported individually as `lookupSlackUserTool`.
## Usage
Spread it into [`createBot({ tools })`](/reference/bot/functions/createBot):
```ts
import { defaultSlackTools } from "@copilotkit/bot-slack";
const bot = createBot({
// …
tools: [...defaultSlackTools, ...myAppTools],
});
```
## lookup_slack_user
Resolves a person to a Slack user ID so the agent can @-mention them properly.
<PropertyReference name="query" type="string" required>
Handle (`atai`), display name (`Atai Barkai`), first name, or email of the person to look up.
</PropertyReference>
**Returns** (to the agent): on success `{ found: true, query, userId, name, handle, email, mention }` — where `mention` is the ready-to-paste `<@U…>` string the agent should put verbatim in its reply; on a miss, `{ found: false, query }` so the agent writes the plain name instead.
Under the hood it calls [`thread.lookupUser(query)`](/reference/bot/classes/Thread), the capability-gated directory search this adapter backs.
## Related
- [defaultSlackContext](/reference/bot/slack/defaultSlackContext) — the companion context entries (tagging procedure)
- [defineBotTool](/reference/bot/functions/defineBotTool) — how these tools are defined
- [slack()](/reference/bot/slack) — the adapter
@@ -0,0 +1,125 @@
---
title: "slack"
description: "The Slack platform adapter factory — Socket Mode ingress via Bolt, Block Kit egress, chat.update streaming, and ack-first opaque-id interactions."
---
## Overview
`slack(opts)` returns a `SlackAdapter` — the Slack implementation of the engine's `PlatformAdapter` boundary. It handles ingress via Bolt (Socket Mode by default), renders the JSX vocabulary to Block Kit within Slack's per-element budgets, streams agent replies via throttled `chat.update`, and decodes interactions to opaque action ids. `@copilotkit/bot-slack` is the only package in the stack that talks to Slack.
For app creation, tokens, and first run, see the [Slack quickstart](/slack).
## Signature
```ts
import { slack } from "@copilotkit/bot-slack";
function slack(opts: SlackAdapterOptions): SlackAdapter;
```
## Parameters
<PropertyReference name="opts" type="SlackAdapterOptions" required>
Adapter configuration.
<PropertyReference name="botToken" type="string" required>
Slack bot token (`xoxb-…`), used for the Web API (posting, updating, history).
</PropertyReference>
<PropertyReference name="appToken" type="string" required>
Slack app-level token (`xapp-…`) with `connections:write`, used for Socket Mode.
</PropertyReference>
<PropertyReference name="socketMode" type="boolean" default="true">
Run over Socket Mode — an outbound WebSocket, no public URL needed. Set `false` for HTTP mode, which requires `signingSecret`.
</PropertyReference>
<PropertyReference name="signingSecret" type="string">
Request signing secret; required when `socketMode` is `false`.
</PropertyReference>
<PropertyReference name="port" type="number" default="0">
HTTP port for non-socket mode; `0` lets the OS assign one. Ignored under Socket Mode.
</PropertyReference>
<PropertyReference name="logLevel" type="LogLevel" default="LogLevel.INFO">
Bolt log level.
</PropertyReference>
<PropertyReference name="interruptEventNames" type="ReadonlySet<string>">
Custom-event names the run renderer treats as agent interrupts.
</PropertyReference>
<PropertyReference name="showToolStatus" type="boolean" default="true">
Surface `:wrench:` / `:white_check_mark:` tool-status rows in the streamed reply while the agent calls tools.
</PropertyReference>
</PropertyReference>
## Return Value
A `SlackAdapter` to pass into [`createBot({ adapters })`](/reference/bot/functions/createBot). It advertises `platform: "slack"`, `ackDeadlineMs: 3000`, and:
```ts
capabilities: {
supportsStreaming: true,
supportsModals: false,
supportsTyping: false,
supportsReactions: false,
maxBlocksPerMessage: 50,
}
```
The capability-gated [`Thread`](/reference/bot/classes/Thread) methods are all backed: `getMessages()` via `conversations.replies`, `lookupUser(query)` via directory search, and `postFile(...)` via `files.uploadV2`. Inbound file uploads are downloaded and delivered to the agent as multimodal content parts.
## Usage
```ts
import { createBot } from "@copilotkit/bot";
import { slack, defaultSlackTools, defaultSlackContext } from "@copilotkit/bot-slack";
const bot = createBot({
adapters: [
slack({
botToken: process.env.SLACK_BOT_TOKEN!, // xoxb-…
appToken: process.env.SLACK_APP_TOKEN!, // xapp-… (Socket Mode)
}),
],
agent: (threadId) => makeAgent(threadId),
tools: [...defaultSlackTools, ...appTools],
context: [...defaultSlackContext, ...appContext],
});
```
## Behavior
### Ingress
The Slack listener pre-filters events to the turns the bot should answer — @-mentions, replies in threads it owns, and DMs — so a single `onMention` handler usually covers everything. Conversation history is rebuilt from Slack (`conversations.replies` / `conversations.history`) on every turn: Slack is the source of truth, so bot restarts don't lose conversations.
### Streaming
[`thread.runAgent()`](/reference/bot/classes/Thread) and `thread.stream(...)` post a placeholder and edit it in place as text arrives: `chat.update` calls are queued per message with a minimum gap between flushes (default **800ms**); long replies roll over into follow-up messages at a soft **3500-character** limit (under Slack's ~4000), breaking at the last newline or space and keeping fenced code blocks whole; dangling markdown (an unclosed fence or bold span) is auto-closed on each flush so the in-flight message always renders. Text is translated per chunk by [`markdownToMrkdwn`](/reference/bot/slack/markdownToMrkdwn).
### Interactions (ack-first)
Every `block_actions` click is acked within Slack's 3-second deadline (`ackDeadlineMs: 3000`), then handled asynchronously. `decodeInteraction` extracts the opaque minted id (`ck:…`), the control's `value`, and the message ref — those are the only things that ride in the Slack payload; handler code, other props, and [`bind()`](/reference/bot/functions/bind) args stay server-side. Unrelated clicks decode to events the bot harmlessly ignores; clicks on actions lost to a restart (with the default in-memory [ActionStore](/reference/bot/types/ActionStore)) are acked but ignored.
### Initialization
The underlying Bolt `App` is constructed with `deferInitialization` — construction is side-effect-free, and `bot.start()` owns initialization (Bolt `init()`, then an awaited `auth.test` to resolve the bot's own user id), so auth and config errors surface to the caller instead of firing in the background.
## What's NOT in v1
- Modals / true batched form submit
- OAuth / multi-workspace install (single bot token only)
- Durable (Redis/DB) [`ActionStore`](/reference/bot/types/ActionStore) — in-memory only; actions expire on restart
- Proactive posting (the bot replies only to turns it's part of)
- Reactions
## Related
- [renderBlockKit](/reference/bot/slack/renderBlockKit) — the JSX → Block Kit mapping and per-element budgets
- [markdownToMrkdwn](/reference/bot/slack/markdownToMrkdwn) — the Markdown → mrkdwn translation
- [defaultSlackTools](/reference/bot/slack/defaultSlackTools) / [defaultSlackContext](/reference/bot/slack/defaultSlackContext) — the shipped built-ins
- [SanitizingHttpAgent](/reference/bot/slack/SanitizingHttpAgent) — connecting to a remote AG-UI backend
- [Slack quickstart](/slack) — app manifest, tokens, first run
@@ -0,0 +1,28 @@
---
title: "markdownToMrkdwn"
description: "Translate GFM Markdown to Slack mrkdwn — bold, links, lists, and tables rendered monospace in a code fence."
---
## Overview
`markdownToMrkdwn` converts GitHub-flavored Markdown — what agents emit — into Slack's `mrkdwn` dialect. The adapter applies it automatically to every streamed chunk and every [`Section`](/reference/bot/components/Section)/[`Markdown`](/reference/bot/components/Markdown) block, so agent output renders correctly without the model knowing about mrkdwn.
## Signature
```ts
import { markdownToMrkdwn } from "@copilotkit/bot-slack";
function markdownToMrkdwn(input: string): string;
```
## Behavior
- **Inline styles** — GFM emphasis is rewritten to mrkdwn (e.g. `**bold**` → `*bold*`); links become mrkdwn `<url|label>` form; lists are normalized.
- **Code passes through** — fenced ``` blocks and inline `` `code` `` are left untouched; they render identically in both flavors.
- **Tables become monospace** — mrkdwn has no table primitive, so GFM tables are column-aligned and wrapped in a code fence to stay readable.
- **Applied per chunk** — during [streaming](/reference/bot/slack), the translation runs on each flushed chunk, paired with the mid-stream auto-closer so partially streamed markdown still renders.
## Related
- [slack()](/reference/bot/slack) — streaming behavior and where this runs
- [defaultSlackContext](/reference/bot/slack/defaultSlackContext) — steers the agent's formatting expectations
@@ -0,0 +1,69 @@
---
title: "renderBlockKit"
description: "Translate the bot-ui BotNode IR to Slack Block Kit, degrading within Slack's per-element budgets (SLACK_LIMITS)."
---
## Overview
`renderBlockKit` translates the [BotNode IR](/reference/bot/types/BotNode) into Block Kit blocks; `renderSlackMessage` additionally extracts a top-level `<Message accent>` into the accent color for an attachment. The adapter calls these for you on every [`thread.post`](/reference/bot/classes/Thread) — reach for them directly when testing components or building custom egress.
## Signature
```ts
import { renderBlockKit, renderSlackMessage, SLACK_LIMITS } from "@copilotkit/bot-slack";
function renderBlockKit(ir: BotNode[]): KnownBlock[];
function renderSlackMessage(ir: BotNode[]): {
blocks: KnownBlock[];
accent?: string;
};
```
## Component mapping
| Component | Block Kit |
|-----------|-----------|
| [`Message`](/reference/bot/components/Message) | the message's `blocks` (or an accent attachment) |
| [`Header`](/reference/bot/components/Header) | `header` |
| [`Section`](/reference/bot/components/Section) / [`Markdown`](/reference/bot/components/Markdown) | `section` (mrkdwn) |
| [`Fields`](/reference/bot/components/Fields) | `section.fields` |
| [`Context`](/reference/bot/components/Context) | `context` |
| [`Actions`](/reference/bot/components/Actions) | `actions` |
| [`Button`](/reference/bot/components/Button) | `button` (`action_id` = minted opaque id) |
| [`Select`](/reference/bot/components/Select) | `static_select` |
| [`Input`](/reference/bot/components/Input) | `plain_text_input` |
| [`Image`](/reference/bot/components/Image) | `image` |
| [`Divider`](/reference/bot/components/Divider) | `divider` |
| [`Table`](/reference/bot/components/Table) | native `table` block |
## Per-element budgets
Slack caps every element. The renderer **degrades instead of failing**: over-long text is truncated with an overflow marker, and the top-level block list is clamped to 50 with an overflow-signal context block appended when blocks had to be dropped. Sub-collections (fields, actions elements, context elements, select options, table rows/columns) are clamped to their caps **without** a marker — an 11th field or 26th button is dropped silently. Nothing ever exceeds a platform limit. The limits ship as `SLACK_LIMITS`:
| Limit | Value | Element |
|-------|-------|---------|
| `blocksPerMessage` | 50 | blocks per message |
| `sectionText` | 3000 | section body chars |
| `headerText` | 150 | header chars |
| `fieldsPerSection` | 10 | fields per section |
| `fieldText` | 2000 | field chars |
| `actionsElements` | 25 | controls per actions row |
| `contextElements` | 10 | elements per context block |
| `buttonText` | 75 | button label chars |
| `actionId` | 255 | `action_id` chars |
| `buttonValue` | 2000 | button value chars |
| `selectOptions` | 100 | options per select |
| `tableColumns` | 20 | columns per table |
| `tableRows` | 100 | rows per table |
| `cellText` | 2000 | table cell chars |
## Accent attachments
Block Kit has no native accent color, so a single top-level `<Message accent="#27AE60">` is surfaced by `renderSlackMessage` as `accent`, and the adapter posts the blocks as an attachment with a colored left bar: `attachments: [{ color, blocks }]`.
## Related
- [slack()](/reference/bot/slack) — the adapter that calls this on every post
- [BotNode](/reference/bot/types/BotNode) — the IR this consumes
- [renderToIR](/reference/bot/functions/renderToIR) — producing the IR from JSX
@@ -0,0 +1,79 @@
---
title: "ActionStore"
description: "The persistence contract behind inline JSX handlers — action snapshots, cold-path rehydration, the in-memory default, and durability tiers."
---
## Overview
`ActionStore` is the persistence contract behind inline interaction handlers. When you post JSX with an `onClick`, the engine mints a **content-stable, opaque id** for the control — `mintId(componentName, path, props)`, a `"ck:" + sha1(…)` prefix — and stores an `ActionSnapshot` describing how to re-derive the handler. What's stamped on the native message is the opaque id plus the control's `value` prop (serialized — up to 2000 chars on Slack); handler code, other props, and [`bind()`](/reference/bot/functions/bind) args never leave the process. Don't put secrets in a control's `value`.
On a click, the `ActionRegistry` resolves the handler from a hot in-memory cache; on a miss it **rehydrates** from the store — load the snapshot, re-render the named component with the frozen props, re-walk to the handler's path.
## Contract
```ts
interface ActionStore {
put(id: string, snap: ActionSnapshot, ttlMs?: number): Promise<void>;
get(id: string): Promise<ActionSnapshot | undefined>;
delete(id: string): Promise<void>;
}
```
<PropertyReference name="put" type="(id: string, snap: ActionSnapshot, ttlMs?: number) => Promise<void>">
Persist a snapshot under the minted id, optionally with a TTL.
</PropertyReference>
<PropertyReference name="get" type="(id: string) => Promise<ActionSnapshot | undefined>">
Load a snapshot; `undefined` means the action is unknown or expired.
</PropertyReference>
<PropertyReference name="delete" type="(id: string) => Promise<void>">
Remove a snapshot.
</PropertyReference>
### ActionSnapshot
<PropertyReference name="component" type="string">
The component to re-render on a cold path.
</PropertyReference>
<PropertyReference name="props" type="unknown">
The frozen, serializable props the component was rendered with.
</PropertyReference>
<PropertyReference name="path" type="(string | number)[]" required>
The path from the rendered tree's root to the handler's node.
</PropertyReference>
<PropertyReference name="boundArgs" type="unknown">
The payload attached via [`bind()`](/reference/bot/functions/bind), if any.
</PropertyReference>
<PropertyReference name="conversationKey" type="string" required>
The conversation the action belongs to.
</PropertyReference>
## Durability tiers
1. **Inline handlers** — re-derivable from the component's serializable props alone; nothing extra to persist. This is why components must be pure functions of their props (see [renderToIR](/reference/bot/functions/renderToIR)).
2. **`bind(handler, args)`** — attaches a handler-specific `args` payload, snapshotted as `boundArgs`. **v1 caveat:** the cold path does not yet inject `boundArgs` on rehydration — after a restart the handler is re-derived from props, so `args` must currently be derivable from props to survive one (see [bind()](/reference/bot/functions/bind)).
3. **A durable store** — required for actions to survive a process restart at all.
## The in-memory default
The default is `InMemoryActionStore` — a `Map` with optional TTL. **It is lost on restart**: clicking a button posted before a restart raises `ActionExpiredError` internally, which [`createBot`](/reference/bot/functions/createBot) swallows rather than crashing the bot — the click is acked but nothing happens, and no message is posted.
A durable store (Redis, a database) is **not shipped in v1** — implement the three-method contract against your own backend and pass it in:
```ts
const bot = createBot({
adapters: [slack({ botToken, appToken })],
actionStore: new RedisActionStore(redisClient), // your implementation
});
```
## Related
- [createBot](/reference/bot/functions/createBot) — the `actionStore` option
- [bind()](/reference/bot/functions/bind) — persisting handler-specific args
- [Button](/reference/bot/components/Button) — where inline handlers live
@@ -0,0 +1,62 @@
---
title: "BotNode"
description: "The serializable intermediate representation (IR) of bot UI — what JSX renders to and what platform adapters translate."
---
## Overview
`BotNode` is the intermediate representation between your JSX and a platform's native format. [`renderToIR`](/reference/bot/functions/renderToIR) lowers a component tree to `BotNode[]`; an adapter (e.g. [`slack()`](/reference/bot/slack)) translates those nodes to its native payload. Because the IR is plain serializable data, it can be snapshotted for [action rehydration](/reference/bot/types/ActionStore) and asserted against in tests.
## Shape
```ts
interface BotNode {
type: string | ComponentFn | symbol; // intrinsic string after rendering
props: Record<string, unknown>;
key?: string | number;
}
type ComponentFn = (
props: Record<string, unknown>,
) => BotNode | BotNode[] | string | null;
type Renderable = string | BotNode | BotNode[] | { raw: unknown };
```
<PropertyReference name="type" type="string | ComponentFn | symbol" required>
Before rendering, a node's `type` may be a component function (your `<IssueCard />`) or the `Fragment` symbol. After [`renderToIR`](/reference/bot/functions/renderToIR), only **intrinsic string types** remain — `"message"`, `"header"`, `"section"`, `"button"`, `"text"`, `"raw"`, … — one per vocabulary component.
</PropertyReference>
<PropertyReference name="props" type="Record<string, unknown>" required>
The node's props, including expanded `children`. Behavior props (`onClick`, `onSelect`, `onSubmit`) ride along here until the engine binds them, rewriting each to its minted opaque id.
</PropertyReference>
<PropertyReference name="key" type="string | number">
Optional stable key, as in other JSX dialects.
</PropertyReference>
## Special node types
- **`text`** — strings and numbers in children become `{ type: "text", props: { value: string } }`.
- **`raw`** — the `{ raw }` escape hatch passes through as `{ type: "raw", props: { value } }`, letting you hand an adapter a native payload (e.g. hand-built Block Kit) without going through the vocabulary.
## Children
```ts
type BotChildren =
| BotNode
| string
| number
| boolean
| null
| undefined
| BotChildren[];
```
Conditionals render nothing (`false` / `null` / `undefined`), so `{cond && <Section>…</Section>}` works as expected.
## Related
- [renderToIR](/reference/bot/functions/renderToIR) — producing the IR
- [Message](/reference/bot/components/Message) — the vocabulary that lowers to these nodes
- [ActionStore](/reference/bot/types/ActionStore) — how handler props are bound and snapshotted
@@ -0,0 +1,71 @@
---
title: "InteractionContext"
description: "The context passed to interaction handlers — the thread, the clicked control's typed value, and the clicking user."
---
## Overview
`InteractionContext` is what every interaction handler receives — inline `onClick` / `onSelect` / `onSubmit` handlers on [components](/reference/bot/components/Button), handlers registered with `bot.onInteraction`, and handlers wrapped with [`bind()`](/reference/bot/functions/bind). It is generic over the clicked control's value type.
## Shape
```ts
type ClickHandler<TValue = unknown> = (
ctx: InteractionContext<TValue>,
) => void | Promise<void>;
interface InteractionContext<TValue = unknown> {
thread: Thread;
message: IncomingMessage;
action: { id: string; value?: TValue };
values: Record<string, unknown>;
user: PlatformUser;
platform: string;
}
```
## Properties
<PropertyReference name="thread" type="Thread" required>
The conversation the interaction happened in — post a reply, [`update`](/reference/bot/classes/Thread) the card in place, or run the agent.
</PropertyReference>
<PropertyReference name="message" type="IncomingMessage" required>
The message the clicked control lives on (`{ text, user, ref, platform }`); `ref` is what you'd pass to `thread.update` to rewrite the card.
</PropertyReference>
<PropertyReference name="action" type="{ id: string; value?: TValue }" required>
The clicked control: its opaque minted `id` and the `value` it carried. For [`Button`](/reference/bot/components/Button), `TValue` is inferred from the `value` prop; [`Select`](/reference/bot/components/Select) and [`Input`](/reference/bot/components/Input) resolve it to `string`; [`bind()`](/reference/bot/functions/bind) sets it to the bound `args`.
</PropertyReference>
<PropertyReference name="values" type="Record<string, unknown>" required>
Reserved for sibling control values. Always `{}` in v1 — no adapter populates it yet.
</PropertyReference>
<PropertyReference name="user" type="PlatformUser" required>
Who clicked (`{ id, name?, handle?, email? }`).
</PropertyReference>
<PropertyReference name="platform" type="string" required>
The active surface, e.g. `"slack"`.
</PropertyReference>
## Handler return type
A `ClickHandler` must return `void` or `Promise<void>`. A concise arrow whose body ends in a value-returning call — like `thread.post(...)`, which returns a `MessageRef` — won't type-check; use a block body:
```tsx
<Button
onClick={async ({ thread }) => {
await thread.post("done");
}}
>
Done
</Button>
```
## Related
- [Button](/reference/bot/components/Button) — typed `value` inference
- [bind()](/reference/bot/functions/bind) — handlers with persisted args
- [Thread](/reference/bot/classes/Thread) — everything you can do with `ctx.thread`
@@ -1,12 +0,0 @@
---
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
*/
}
@@ -1,102 +0,0 @@
---
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>
@@ -1,108 +0,0 @@
---
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>
@@ -1,203 +0,0 @@
---
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>
@@ -1,12 +0,0 @@
---
title: "LangGraphAGUIAgent"
description: "LangGraphAGUIAgent 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_agui_agent.py
*/
}
@@ -1,181 +0,0 @@
---
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, LangGraphAGUIAgent
from my_agent.agent import graph
sdk = CopilotKitRemoteEndpoint(
agents=[
LangGraphAGUIAgent(
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, LangGraphAGUIAgent
from my_agent.agent import graph
sdk = CopilotKitRemoteEndpoint(
agents=lambda context: [
LangGraphAGUIAgent(
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>
+111 -10
View File
@@ -8,8 +8,11 @@
import fs from "fs";
import path from "path";
import React from "react";
import matter from "gray-matter";
import { Slack } from "lucide-react";
import type * as PageTree from "fumadocs-core/page-tree";
import { CopilotKitMark } from "@/components/copilotkit-mark";
import { safeExistsSync, safeReadFileSync } from "@/lib/safe-fs";
export const REFERENCE_CONTENT_DIR = path.join(
@@ -23,7 +26,7 @@ export const REFERENCE_CONTENT_DIR = path.join(
// `reference-version-selector.tsx` (a `Record<ReferenceVersion, string>`,
// so a missing label is a compile error), and create a
// `src/content/reference/<id>/` folder.
export const REFERENCE_VERSIONS = ["v2", "v1", "core"] as const;
export const REFERENCE_VERSIONS = ["v2", "v1", "core", "bot"] as const;
export type ReferenceVersion = (typeof REFERENCE_VERSIONS)[number];
/** The root SDK whose content lives directly under `reference/`. */
@@ -32,34 +35,41 @@ const ROOT_VERSION: ReferenceVersion = "v2";
export const REFERENCE_CATEGORIES = [
"Components",
"Hooks",
"Functions",
"Classes",
"Types",
"Enums",
"SDKs",
"Slack",
] as const;
export type ReferenceCategory = (typeof REFERENCE_CATEGORIES)[number];
type ReferenceSubdir =
| "components"
| "hooks"
| "functions"
| "classes"
| "types"
| "enums"
| "sdk";
| "sdk"
| "slack";
const VERSION_SUBDIRS: Record<ReferenceVersion, ReferenceSubdir[]> = {
v2: ["components", "hooks", "sdk"],
v2: ["components", "hooks"],
v1: ["components", "hooks", "classes", "sdk"],
core: ["classes", "types", "enums"],
bot: ["components", "functions", "classes", "types", "slack"],
};
const CATEGORY_BY_SUBDIR: Record<ReferenceSubdir, ReferenceCategory> = {
components: "Components",
hooks: "Hooks",
functions: "Functions",
classes: "Classes",
types: "Types",
enums: "Enums",
sdk: "SDKs",
slack: "Slack",
};
export type ReferenceItem = {
@@ -245,9 +255,106 @@ export function loadReferenceVersionItems(
);
}
function itemToPage(item: ReferenceItem): PageTree.Item {
return { type: "page", name: item.title, url: item.url };
}
// Package separators carry the package's mark. Mirror page-tree-bridge:
// merge icon + label into the separator's `name` (fumadocs renders
// `[item.icon, item.name]` as a keyless child array, so the split
// `icon` prop triggers React's key warning).
function packageSeparator(
icon: React.ReactNode,
label: string,
): PageTree.Separator {
return {
type: "separator",
name: React.createElement(
React.Fragment,
null,
React.isValidElement(icon)
? React.cloneElement(icon, { key: "icon" })
: icon,
React.createElement("span", { key: "label" }, label),
),
};
}
/**
* The Bots tab groups the sidebar by package, not by category: a
* `@copilotkit/bot` section with collapsed kind-folders (Components /
* Functions / Classes / Types), then a flat `@copilotkit/bot-slack`
* section listing the adapter's own exports (the `slack/` subdir).
*/
function buildBotPageTree(): PageTree.Root {
const kindFolder = (
name: string,
subdir: ReferenceSubdir,
): PageTree.Folder[] => {
const items = loadReferenceItems("bot", subdir);
if (items.length === 0) return [];
return [
{
type: "folder",
name,
defaultOpen: false,
children: items.map(itemToPage),
},
];
};
// Explicit order: the adapter factory first, then rendering, then the
// supporting exports. Anything new lands after, in filesystem order.
const SLACK_ORDER = [
"slack",
"slack/renderBlockKit",
"slack/markdownToMrkdwn",
"slack/defaultSlackTools",
"slack/defaultSlackContext",
"slack/SanitizingHttpAgent",
];
const slackItems = [...loadReferenceItems("bot", "slack")].sort((a, b) => {
const ai = SLACK_ORDER.indexOf(a.slug);
const bi = SLACK_ORDER.indexOf(b.slug);
return (
(ai === -1 ? SLACK_ORDER.length : ai) -
(bi === -1 ? SLACK_ORDER.length : bi)
);
});
const slackCoreFolder: PageTree.Folder[] =
slackItems.length === 0
? []
: [
{
type: "folder",
name: "Core",
defaultOpen: false,
children: slackItems.map(itemToPage),
},
];
return {
name: "Reference",
children: [
packageSeparator(React.createElement(CopilotKitMark), "@copilotkit/bot"),
...kindFolder("Components", "components"),
...kindFolder("Functions", "functions"),
...kindFolder("Classes", "classes"),
...kindFolder("Types", "types"),
packageSeparator(
React.createElement(Slack, { size: 16 }),
"@copilotkit/bot-slack",
),
...slackCoreFolder,
],
};
}
export function buildReferencePageTree(
version: ReferenceVersion,
): PageTree.Root {
if (version === "bot") return buildBotPageTree();
const allItems = loadReferenceVersionItems(version);
return {
name: "Reference",
@@ -258,13 +365,7 @@ export function buildReferencePageTree(
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,
}),
),
...categoryItems.map(itemToPage),
];
}),
};