chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
+284
View File
@@ -0,0 +1,284 @@
# CopilotKit Architecture Guide
CopilotKit lets you add AI agents to your app. You write hooks (React/Angular) or use the core API (vanilla JS), CopilotKit handles the rest — connecting your UI to any AI agent framework.
---
## The 30-Second Version
```mermaid
graph TB
subgraph Your App
A[React / Angular / Vanilla JS]
end
subgraph Your Server
B[CopilotKit Runtime]
end
subgraph Any Agent Framework
C[LangGraph / CrewAI / Mastra / Custom]
end
A -->|HTTP POST| B
B -->|AG-UI Events| C
C -->|AG-UI Events| B
B -->|SSE Stream| A
```
That's it. Your app talks to a runtime on your server. The runtime talks to an AI agent. They communicate using **AG-UI** — an event-based protocol (think: "text is streaming", "agent wants to call a tool", "state changed").
---
## The Three Layers
### Layer 1: Frontend (your app)
You use hooks/services to wire up your app — registering tools agents can call, providing context, and getting agent instances.
### Layer 2: Runtime (your server)
A few lines create the backend that receives requests from the frontend, runs agents, and streams events back.
### Layer 3: Agent (any framework)
The agent is anything that speaks AG-UI protocol. CopilotKit has integrations for 13+ frameworks, or you build your own.
---
## How a Message Flows Through the System
```mermaid
sequenceDiagram
participant User
participant App as Your App
participant Core as CopilotKitCore
participant Runtime as CopilotRuntime
participant Agent as AI Agent
Note over App: Setup (on mount)
App->>Core: Provider creates Core
Core->>Runtime: GET /info (fetch agent list)
Runtime-->>Core: [{ name, description }]
App->>Core: Hooks register tools + context
Note over User: User sends message
User->>App: Types message, hits send
App->>Core: Gets agent instance
Core->>Runtime: POST /agent/{id}/run
Runtime->>Agent: AgentRunner.run()
Note over Agent: Events stream back
Agent-->>Runtime: TEXT_MESSAGE_START
Agent-->>Runtime: TEXT_MESSAGE_CONTENT (streaming)
Agent-->>Runtime: TEXT_MESSAGE_END
Runtime-->>Core: SSE event stream
Core-->>App: Subscribers fire, UI re-renders
App-->>User: Chat shows streaming response
Note over Agent: Tool call (optional)
Agent-->>Runtime: TOOL_CALL_START + ARGS
Runtime-->>Core: SSE events
Core->>Core: Execute frontend tool
Core-->>Runtime: TOOL_CALL_RESULT
Runtime->>Agent: Agent continues
Agent-->>Runtime: RUN_FINISHED
```
---
## Guides
| Guide | What you'll learn |
| ------------------------------------------ | ------------------------------------------------------- |
| [React Setup](setup-react.md) | Provider, hooks, chat UI — full React integration |
| [Angular Setup](setup-angular.md) | DI tokens, services, signals — full Angular integration |
| [Vanilla JS Setup](setup-vanilla.md) | CopilotKitCore API without any framework |
| [Runtime / Backend](setup-runtime.md) | Express/Hono endpoints, agents, runners, middleware |
| [Multi-Agent Patterns](multi-agent.md) | Multiple agents, routing, agent-specific tools |
| [Pluggable Architecture](plugin-points.md) | Every optional extension point with diagrams |
---
## Package Dependency Map
```mermaid
graph BT
subgraph AG-UI Protocol
core["@ag-ui/core<br/><i>Types + Event schemas</i>"]
client["@ag-ui/client<br/><i>AbstractAgent, HttpAgent, Middleware</i>"]
encoder["@ag-ui/encoder<br/><i>SSE / Binary / Protobuf encoding</i>"]
client --> core
encoder --> core
end
subgraph CopilotKit Packages
shared["@copilotkit/shared<br/><i>Utils, types, constants</i>"]
core["@copilotkit/core<br/><i>CopilotKitCore orchestrator</i>"]
reactcore["@copilotkit/react-core<br/><i>Provider + hooks</i>"]
reactui["@copilotkit/react-ui<br/><i>Chat, Popup, Sidebar</i>"]
reacttextarea["@copilotkit/react-textarea<br/><i>AI text editing</i>"]
gql["@copilotkit/runtime-client-gql<br/><i>urql GraphQL client</i>"]
runtime["@copilotkit/runtime<br/><i>Express/Hono server + AgentRunner + Built-in agent</i>"]
core --> shared
reactcore --> core
reactcore --> gql
reactui --> reactcore
reacttextarea --> reactcore
runtime --> shared
reactcore -.-> client
gql --> shared
end
```
---
## AG-UI Protocol at a Glance
AG-UI is the communication contract between agents and UIs. Everything is an **event** streamed over SSE.
```mermaid
graph LR
subgraph Lifecycle
RS[RUN_STARTED] --> SS[STEP_STARTED]
SF[STEP_FINISHED] --> RF[RUN_FINISHED]
end
subgraph Text
TMS[TEXT_MESSAGE_START] --> TMC[TEXT_MESSAGE_CONTENT]
TMC --> TME[TEXT_MESSAGE_END]
end
subgraph Tools
TCS[TOOL_CALL_START] --> TCA[TOOL_CALL_ARGS]
TCA --> TCE[TOOL_CALL_END]
TCE --> TCR[TOOL_CALL_RESULT]
end
subgraph State
SNP[STATE_SNAPSHOT]
SD[STATE_DELTA]
end
SS --> TMS
TME --> TCS
TCR --> SF
```
| Package | Role | Key exports |
| ---------------- | ---------------------------------------- | ----------------------------------------------------------------- |
| `@ag-ui/core` | The contract — event types + data shapes | `EventType` enum, Zod schemas, `RunAgentInput`, `Message`, `Tool` |
| `@ag-ui/client` | Client-side agent abstraction | `AbstractAgent`, `HttpAgent`, `Middleware`, re-exports core |
| `@ag-ui/encoder` | Serializes events for transport | `EventEncoder` (SSE, binary, protobuf) |
| `@ag-ui/proto` | Protobuf binary transport | `encode()`, `decode()` |
13+ framework integrations at `ag-ui/integrations/`: LangGraph, CrewAI, Mastra, Vercel AI SDK, Agno, AWS Strands, LlamaIndex, and more.
---
## Quick Reference
**"I want to..."** — here's where to look:
### Setup & Configuration
| Goal | Package | Key file / API |
| -------------------------------- | ------------------------ | -------------------------------------------- |
| Set up a React app | `@copilotkit/react-core` | `<CopilotKit runtimeUrl="...">` provider |
| Set up an Angular app | `@copilotkit/angular` | `provideCopilotKit({ runtimeUrl })` DI token |
| Set up vanilla JS | `@copilotkit/core` | `new CopilotKitCore({ runtimeUrl })` |
| Set up the backend (Express) | `@copilotkit/runtime` | `createCopilotEndpointExpress({ runtime })` |
| Set up the backend (Hono) | `@copilotkit/runtime` | `createCopilotEndpointHono({ runtime })` |
| Configure authentication headers | Provider / Core config | `headers: { Authorization: "Bearer ..." }` |
| Forward cookies to runtime | Provider / Core config | `credentials: "include"` |
### Agent Communication
| Goal | Package | Key file / API |
| ------------------------------- | ------------------------ | ---------------------------------------------- |
| Get an agent instance (React) | `@copilotkit/react-core` | `useAgent({ agentId })` |
| Get an agent instance (Angular) | `@copilotkit/angular` | `AgentStore` with signals |
| Get an agent instance (vanilla) | `@copilotkit/core` | `copilotkit.getAgent(id)` |
| Run an agent | Core / hooks | `copilotkit.runAgent({ agent })` |
| Use multiple agents | Runtime config | `agents: { research: agent1, coding: agent2 }` |
| Agent-specific tools | `useFrontendTool` | `{ name, agentId: "specific-agent", handler }` |
| Shared context for all agents | `useAgentContext` | `useAgentContext("desc", value)` |
### Tools & Interactivity
| Goal | Package | Key file / API |
| ------------------------------- | ------------------------ | ------------------------------------------------ |
| Register a tool agents can call | `react-core` or `react` | `useFrontendTool({ name, parameters, handler })` |
| Give agents context data | `react-core` or `react` | `useCopilotReadable()` / `useAgentContext()` |
| Share state with an agent (V1) | `@copilotkit/react-core` | `useCoAgent({ name, initialState })` |
| Custom UI for tool execution | Provider or hook | `renderToolCalls` / `useRenderToolCall()` |
| Require human approval | Provider or hook | `humanInTheLoop` / `useHumanInTheLoop()` |
| Auto-generate suggestions | Hook | `useConfigureSuggestions({ instructions })` |
| Inject system instructions (V1) | `@copilotkit/react-core` | `useCopilotAdditionalInstructions()` |
### UI Components
| Goal | Package | Component |
| ---------------------- | ---------------------------- | ------------------- |
| Full chat interface | `@copilotkit/react-ui` | `<CopilotChat>` |
| Floating popup chat | `@copilotkit/react-ui` | `<CopilotPopup>` |
| Side panel chat | `@copilotkit/react-ui` | `<CopilotSidebar>` |
| Inline panel chat | `@copilotkit/react-ui` | `<CopilotPanel>` |
| AI text autocompletion | `@copilotkit/react-textarea` | `<CopilotTextarea>` |
### Backend & Runtime
| Goal | Package | Key file / API |
| ---------------------------- | --------------------------- | ---------------------------------------------------- |
| Custom agent runner | `@copilotkit/runtime` | Extend `AgentRunner` abstract class |
| Persistent agent state | `@copilotkit/sqlite-runner` | `SQLiteAgentRunner` |
| Request/response middleware | `CopilotRuntime` options | `beforeRequestMiddleware` / `afterRequestMiddleware` |
| Audio transcription | `CopilotRuntime` options | `transcriptionService` |
| Voice (speech-to-text / TTS) | `@copilotkit/voice` | Voice services |
| Build a custom agent | `@copilotkit/sdk-js` | LangGraph / LangChain helpers |
### Debugging & Internals
| Goal | Package | Key file / API |
| -------------------------------- | --------------------------------- | -------------------------------------------------------------- |
| Understand event types | `@ag-ui/core` | `src/events.ts``EventType` enum |
| Understand the agent abstraction | `@ag-ui/client` | `src/agent/agent.ts``AbstractAgent` |
| See how an integration works | `ag-ui/integrations/{framework}/` | Each extends `AbstractAgent` |
| Understand the core orchestrator | `@copilotkit/core` | `src/core/core.ts``CopilotKitCore` |
| Debug agent interactions | `@copilotkit/web-inspector` | Lit web component, enabled via `showDevConsole` |
| Subscribe to lifecycle events | Core API | `copilotkit.subscribe({ onError, onToolExecutionStart, ... })` |
---
## Monorepo Structure
```
cpk/
├── ag-ui/ # AG-UI Protocol (open standard)
│ ├── sdks/typescript/packages/
│ │ ├── core/ # @ag-ui/core — types + events
│ │ ├── client/ # @ag-ui/client — AbstractAgent, HttpAgent
│ │ ├── encoder/ # @ag-ui/encoder — SSE/binary encoding
│ │ └── proto/ # @ag-ui/proto — protobuf
│ └── integrations/ # 13+ framework adapters
│ ├── langgraph/
│ ├── crewai/
│ ├── mastra/
│ └── ...
└── CopilotKit/ # CopilotKit Product
└── packages/ # All packages flat under @copilotkit/ scope
├── shared/ # @copilotkit/shared — utils, types, constants
├── core/ # @copilotkit/core — CopilotKitCore orchestrator
├── react-core/ # @copilotkit/react-core — provider + hooks
├── react-ui/ # @copilotkit/react-ui — chat components
├── react-textarea/ # @copilotkit/react-textarea — AI text editing
├── runtime/ # @copilotkit/runtime — Express/Hono server + AgentRunner + Built-in agent
├── runtime-client-gql/ # @copilotkit/runtime-client-gql — urql GraphQL client
├── angular/ # @copilotkit/angular — Angular integration
├── voice/ # @copilotkit/voice — voice support
├── web-inspector/ # @copilotkit/web-inspector — debug console
├── sqlite-runner/ # @copilotkit/sqlite-runner — persistent AgentRunner
└── sdk-js/ # @copilotkit/sdk-js — LangGraph/LangChain helpers
```
+450
View File
@@ -0,0 +1,450 @@
# Multi-Agent Patterns Guide
This guide shows how to use multiple agents in CopilotKit — from basic routing to agent-specific tools and shared context.
---
## How Multi-Agent Routing Works
```mermaid
sequenceDiagram
participant React as React App
participant Core as CopilotKitCore
participant Runtime as CopilotRuntime
participant Research as Research Agent
participant Coding as Coding Agent
Note over React: On mount
Core->>Runtime: GET /info
Runtime-->>Core: agents: [{ id: "research" }, { id: "coding" }]
Core->>Core: Create ProxiedAgent for each
Note over React: User picks "research"
React->>Core: useAgent({ agentId: "research" })
Core-->>React: ProxiedAgent(research)
Note over React: User sends message
React->>Core: runAgent({ agent: researchAgent })
Core->>Runtime: POST /agent/research/run
Runtime->>Research: runner.run()
Research-->>React: SSE events
Note over React: User switches to "coding"
React->>Core: useAgent({ agentId: "coding" })
Core-->>React: ProxiedAgent(coding)
React->>Core: runAgent({ agent: codingAgent })
Core->>Runtime: POST /agent/coding/run
Runtime->>Coding: runner.run()
Coding-->>React: SSE events
```
---
## Backend: Register Multiple Agents
```typescript
import { CopilotRuntime } from "@copilotkit/runtime";
import { createCopilotEndpointExpress } from "@copilotkit/runtime/express";
const runtime = new CopilotRuntime({
agents: {
// Each key is the agent ID
default: generalAgent, // Fallback agent
research: researchAgent, // Specialist for research
coding: codingAgent, // Specialist for code
writing: writingAgent, // Specialist for content
},
});
app.use("/api/copilotkit", createCopilotEndpointExpress({ runtime }));
```
The runtime exposes each agent at its own endpoint:
| Agent ID | Run Endpoint |
| ---------- | -------------------------- |
| `default` | `POST /agent/default/run` |
| `research` | `POST /agent/research/run` |
| `coding` | `POST /agent/coding/run` |
| `writing` | `POST /agent/writing/run` |
```mermaid
graph LR
subgraph "Runtime Agent Map"
M["agents: {<br/> default: Agent,<br/> research: Agent,<br/> coding: Agent<br/>}"]
end
subgraph Endpoints
E1["POST /agent/default/run"]
E2["POST /agent/research/run"]
E3["POST /agent/coding/run"]
end
subgraph Agent Instances
A1["General Agent"]
A2["Research Agent"]
A3["Coding Agent"]
end
E1 -->|"agents['default']"| A1
E2 -->|"agents['research']"| A2
E3 -->|"agents['coding']"| A3
```
---
## Frontend: Select an Agent
### React
```tsx
import { useAgent } from "@copilotkit/react-core";
function ResearchPanel() {
// Gets the "research" agent
const { agent } = useAgent({ agentId: "research" });
const sendMessage = async (text: string) => {
agent.addMessage({ id: crypto.randomUUID(), role: "user", content: text });
await copilotKit.runAgent({ agent });
};
return <div>{/* research UI */}</div>;
}
function CodingPanel() {
// Gets the "coding" agent
const { agent } = useAgent({ agentId: "coding" });
// ...
}
```
### Using CopilotChat with agent IDs
```tsx
import { CopilotChat } from "@copilotkit/react-core";
function App() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit">
<div style={{ display: "flex" }}>
{/* Two separate chats, each talking to a different agent */}
<CopilotChat agentId="research" threadId="research-1" />
<CopilotChat agentId="coding" threadId="coding-1" />
</div>
</CopilotKitProvider>
);
}
```
### Angular
```typescript
@Component({
/* ... */
})
export class MultiAgentComponent {
private copilotKit = inject(CopilotKit);
researchStore = this.copilotKit.getAgentStore("research");
codingStore = this.copilotKit.getAgentStore("coding");
}
```
### Vanilla JS
```typescript
const researchAgent = copilotKit.getAgent("research");
const codingAgent = copilotKit.getAgent("coding");
// Each agent has its own messages, state, and thread
await copilotKit.runAgent({ agent: researchAgent });
await copilotKit.runAgent({ agent: codingAgent });
```
---
## The DEFAULT_AGENT_ID
When you don't specify an `agentId`, CopilotKit uses `"default"`:
```typescript
// These are equivalent:
useAgent(); // Uses "default"
useAgent({ agentId: "default" }); // Explicit
// Your backend must have a "default" agent:
const runtime = new CopilotRuntime({
agents: {
default: myAgent, // This is required if any component omits agentId
},
});
```
---
## Agent Discovery
On mount, the frontend fetches available agents from the runtime:
```mermaid
sequenceDiagram
participant Core as CopilotKitCore
participant Runtime as CopilotRuntime
Core->>Runtime: GET /info
Runtime-->>Core: { agents: { research: { description: "..." }, coding: { description: "..." } } }
Core->>Core: Create ProxiedAgent for each
Core->>Core: Notify subscribers (onAgentsChanged)
```
You can react to agent changes:
```typescript
copilotKit.subscribe({
onAgentsChanged: ({ agents }) => {
console.log("Available agents:", Object.keys(agents));
// e.g. ["default", "research", "coding"]
},
});
```
---
## Agent-Specific Tools
Tools can be scoped to specific agents:
```tsx
// This tool is available to ALL agents
useFrontendTool({
name: "getCurrentTime",
handler: async () => new Date().toISOString(),
});
// This tool is ONLY available to the "research" agent
useFrontendTool({
name: "searchPapers",
agentId: "research",
parameters: z.object({ query: z.string() }),
handler: async ({ query }) => await searchPapers(query),
});
// This tool is ONLY available to the "coding" agent
useFrontendTool({
name: "runCode",
agentId: "coding",
parameters: z.object({ code: z.string(), language: z.string() }),
handler: async ({ code, language }) => await executeCode(code, language),
});
```
```mermaid
graph TB
subgraph "Tool Registry"
GT["getCurrentTime<br/><i>All agents</i>"]
SP["searchPapers<br/><i>research only</i>"]
RC["runCode<br/><i>coding only</i>"]
end
subgraph Agents
RA["research agent"]
CA["coding agent"]
end
GT --> RA
GT --> CA
SP --> RA
RC --> CA
```
---
## Shared Context
Context is shared across all agents by default:
```tsx
function App() {
// Both research and coding agents can see this
useAgentContext("Current user", { name: "Alice", role: "developer" });
useAgentContext("Current project", {
name: "my-app",
language: "TypeScript",
});
return (
<>
<CopilotChat agentId="research" />
<CopilotChat agentId="coding" />
</>
);
}
```
```mermaid
graph TB
subgraph "Shared Context"
C1["Current user: Alice"]
C2["Current project: my-app"]
end
subgraph Agents
RA["research agent"]
CA["coding agent"]
end
C1 --> RA
C1 --> CA
C2 --> RA
C2 --> CA
```
---
## Thread Isolation
Each agent conversation runs on its own thread:
```tsx
// These are separate conversations with separate histories
<CopilotChat agentId="research" threadId="research-thread-1" />
<CopilotChat agentId="coding" threadId="coding-thread-1" />
```
```mermaid
graph LR
subgraph "Thread: research-1"
RM1["User: Find papers on AI"]
RM2["Agent: Here are 5 papers..."]
end
subgraph "Thread: coding-1"
CM1["User: Write a sort function"]
CM2["Agent: Here's a quicksort..."]
end
RM1 --> RM2
CM1 --> CM2
```
Each thread maintains its own:
- Message history
- Agent state
- Running status
---
## Full Example: Multi-Agent Dashboard
### Backend
```typescript
import { CopilotRuntime } from "@copilotkit/runtime";
import { createCopilotEndpointExpress } from "@copilotkit/runtime/express";
import { BuiltInAgent } from "@copilotkit/runtime/v2";
const agents = {
default: new BuiltInAgent({
model: "openai/gpt-4o",
systemPrompt: "You are a general assistant.",
}),
research: new BuiltInAgent({
model: "openai/gpt-4o",
systemPrompt:
"You are a research specialist. Search for papers and summarize findings.",
}),
coding: new BuiltInAgent({
model: "openai/gpt-4o",
systemPrompt: "You are a coding expert. Write clean, tested code.",
}),
};
const runtime = new CopilotRuntime({ agents });
app.use("/api/copilotkit", createCopilotEndpointExpress({ runtime }));
```
### Frontend (React)
```tsx
import {
CopilotKitProvider,
CopilotChat,
useAgent,
useFrontendTool,
useAgentContext,
} from "@copilotkit/react-core";
import { z } from "zod";
export default function App() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit">
<SharedContext />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<ResearchPanel />
<CodingPanel />
</div>
</CopilotKitProvider>
);
}
// Shared context — all agents see this
function SharedContext() {
useAgentContext("Current project", {
name: "my-saas-app",
stack: "React + Node.js + PostgreSQL",
description: "A SaaS platform for team collaboration",
});
return null;
}
// Research agent with its own tools
function ResearchPanel() {
useFrontendTool({
name: "saveFindings",
agentId: "research",
description: "Save research findings to the knowledge base",
parameters: z.object({
title: z.string(),
summary: z.string(),
sources: z.array(z.string()),
}),
handler: async ({ title, summary, sources }) => {
await knowledgeBase.save({ title, summary, sources });
return "Saved to knowledge base";
},
});
return (
<div>
<h2>Research Assistant</h2>
<CopilotChat agentId="research" />
</div>
);
}
// Coding agent with its own tools
function CodingPanel() {
useFrontendTool({
name: "createFile",
agentId: "coding",
description: "Create a new file in the project",
parameters: z.object({
path: z.string(),
content: z.string(),
}),
handler: async ({ path, content }) => {
await fileSystem.write(path, content);
return `Created ${path}`;
},
});
return (
<div>
<h2>Coding Assistant</h2>
<CopilotChat agentId="coding" />
</div>
);
}
```
+521
View File
@@ -0,0 +1,521 @@
# Pluggable Architecture Guide
CopilotKit is built around extension points. Almost everything is optional and replaceable. This guide catalogs **every** pluggable part, where it's configured, and what happens when you don't provide it.
---
## Overview: All Extension Points
```mermaid
graph TB
subgraph "Frontend (React / Angular / Vanilla)"
FT["Frontend Tools<br/><i>Functions agents can call</i>"]
CTX["Agent Context<br/><i>Data agents can read</i>"]
RTC["Tool Call Renderers<br/><i>Custom UI for tool calls</i>"]
HIL["Human-in-the-Loop<br/><i>Approval before execution</i>"]
RAM["Activity Renderers<br/><i>Custom activity messages</i>"]
RCM["Custom Message Renderers<br/><i>Inject UI before/after messages</i>"]
SUG["Suggestions Config<br/><i>AI or static suggestions</i>"]
SUBS["Event Subscribers<br/><i>React to lifecycle events</i>"]
end
subgraph "Backend (Runtime)"
BM["Before Middleware<br/><i>Auth, logging, transforms</i>"]
AM["After Middleware<br/><i>Post-processing</i>"]
RUNNER["Agent Runner<br/><i>How agents execute</i>"]
TS["Transcription Service<br/><i>Audio → text</i>"]
end
subgraph "Agent Level"
MW["AG-UI Middleware<br/><i>Intercept agent pipeline</i>"]
end
```
---
## Frontend Extension Points
### 1. Frontend Tools
**What:** Functions in your app that agents can call during a conversation.
**Where configured:**
- React: `useFrontendTool()` hook or `frontendTools` provider prop
- Angular: `copilotKit.addTool()` or `tools` in config
- Vanilla: `copilotKit.addTool()`
**Default when not provided:** No tools — agent can only send text messages.
```typescript
// Type signature
type FrontendTool<T> = {
name: string;
description?: string;
parameters?: z.ZodType<T>;
handler?: (args: T, context: FrontendToolHandlerContext) => Promise<unknown>;
followUp?: boolean; // Re-run agent after tool completes
agentId?: string; // Scope to specific agent
};
```
```mermaid
sequenceDiagram
participant Agent
participant Core as CopilotKitCore
participant Tool as Your Tool Handler
Agent->>Core: TOOL_CALL_START { name: "myTool" }
Agent->>Core: TOOL_CALL_ARGS { ... }
Core->>Tool: handler(args)
Tool-->>Core: result
Core->>Agent: TOOL_CALL_RESULT
opt followUp = true
Core->>Agent: Re-run agent with result
end
```
---
### 2. Agent Context
**What:** JSON data that gets sent to agents as context (like "the user is on the settings page").
**Where configured:**
- React: `useAgentContext()` hook
- Angular / Vanilla: `copilotKit.addContext()` / `removeContext()`
**Default when not provided:** No extra context — agent only sees messages and tool definitions.
```typescript
type AgentContextInput = {
description: string; // Human-readable label
value: JsonSerializable; // Any JSON value
};
```
---
### 3. Tool Call Renderers
**What:** Custom React components that render while a tool is being called — showing progress, args, and results.
**Where configured:**
- React: `useRenderToolCall()` hook or `renderToolCalls` provider prop
- Angular: `renderToolCalls` in config
**Default when not provided:** Generic built-in rendering.
```typescript
type ReactToolCallRenderer<T> = {
name: string; // Tool name to render
args: z.ZodSchema<T>; // Schema for type-safe args
agentId?: string; // Scope to specific agent
render: React.ComponentType<
| { status: "in-progress"; args: Partial<T>; result: undefined }
| { status: "executing"; args: T; result: undefined }
| { status: "complete"; args: T; result: string }
>;
};
```
```mermaid
graph LR
IP["in-progress<br/><i>Args streaming in<br/>Partial&lt;T&gt; available</i>"]
EX["executing<br/><i>Handler running<br/>Full args available</i>"]
CO["complete<br/><i>Result available</i>"]
IP --> EX --> CO
```
---
### 4. Human-in-the-Loop
**What:** Tools that pause and wait for user input before continuing. The user sees a custom UI with approve/deny buttons.
**Where configured:**
- React: `useHumanInTheLoop()` hook or `humanInTheLoop` provider prop
- Angular: `humanInTheLoop` in config
**Default when not provided:** No approval required — tools execute immediately.
```typescript
type ReactHumanInTheLoop<T> = Omit<FrontendTool<T>, "handler"> & {
render: React.ComponentType<{
args: T;
status: "in-progress" | "executing" | "complete";
respond: (result: unknown) => Promise<void>; // Call this to approve/deny
}>;
};
```
```mermaid
sequenceDiagram
participant Agent
participant Core as CopilotKitCore
participant UI as Your Approval UI
participant User
Agent->>Core: TOOL_CALL { name: "deleteUser" }
Core->>UI: Render with status: "executing"
UI->>User: "Delete user X?"
User->>UI: Clicks "Approve"
UI->>Core: respond("approved")
Core->>Agent: TOOL_CALL_RESULT
Agent->>Agent: Continues
```
---
### 5. Activity Message Renderers
**What:** Custom UI for structured activity messages (non-chat messages like progress indicators or MCP app outputs).
**Where configured:**
- React: `useRenderActivityMessage()` hook or `renderActivityMessages` provider prop
**Default when not provided:** Built-in MCP Apps renderer is included. Other activity types show generic display.
```typescript
type ReactActivityMessageRenderer<T> = {
activityType: string; // Use "*" for wildcard
agentId?: string;
content: z.ZodSchema<T>;
render: React.ComponentType<{
activityType: string;
content: T;
message: ActivityMessage;
agent: AbstractAgent | undefined;
}>;
};
```
---
### 6. Custom Message Renderers
**What:** Inject custom UI before or after specific messages (e.g., add a "copy" button, show state snapshots).
**Where configured:**
- React: `useRenderCustomMessages()` hook or `renderCustomMessages` provider prop
**Default when not provided:** No custom rendering — standard message display.
```typescript
type ReactCustomMessageRenderer = {
agentId?: string;
render: React.ComponentType<{
message: Message;
position: "before" | "after";
runId: string;
messageIndex: number;
agentId: string;
stateSnapshot: any;
}> | null;
};
```
---
### 7. Suggestions Configuration
**What:** Configure AI-generated or static prompt suggestions shown to users.
**Where configured:**
- React: `useConfigureSuggestions()` hook
- Core: `suggestionsConfig` in config
**Default when not provided:** No suggestions.
```typescript
// AI-generated suggestions
type DynamicSuggestionsConfig = {
instructions: string; // What to suggest
minSuggestions?: number; // Default: 1
maxSuggestions?: number; // Default: 3
available?: SuggestionAvailability; // When to show
providerAgentId?: string; // Which agent generates them
consumerAgentId?: string; // Which agent receives them ("*" = all)
};
// Static suggestions
type StaticSuggestionsConfig = {
suggestions: Array<{ title: string; message: string }>;
available?: SuggestionAvailability;
consumerAgentId?: string;
};
type SuggestionAvailability =
| "before-first-message" // Default for static
| "after-first-message" // Default for dynamic
| "always"
| "disabled";
```
```mermaid
graph TB
subgraph "Suggestion Types"
DYN["Dynamic<br/><i>AI generates suggestions<br/>from instructions</i>"]
STA["Static<br/><i>You provide fixed<br/>suggestion list</i>"]
end
subgraph "Availability"
BFM["before-first-message"]
AFM["after-first-message"]
ALW["always"]
DIS["disabled"]
end
DYN -.->|default| AFM
STA -.->|default| BFM
```
---
### 8. Event Subscribers
**What:** Listen to lifecycle events — connection status, tool execution, agent changes, errors.
**Where configured:**
- Any: `copilotKit.subscribe(subscriber)`
- Returns: `{ unsubscribe() }` for cleanup
**Default when not provided:** No listeners — events still fire internally.
```typescript
type CopilotKitCoreSubscriber = {
onRuntimeConnectionStatusChanged?: (event) => void;
onToolExecutionStart?: (event) => void;
onToolExecutionEnd?: (event) => void;
onAgentsChanged?: (event) => void;
onContextChanged?: (event) => void;
onSuggestionsChanged?: (event) => void;
onSuggestionsStartedLoading?: (event) => void;
onSuggestionsFinishedLoading?: (event) => void;
onPropertiesChanged?: (event) => void;
onHeadersChanged?: (event) => void;
onError?: (event) => void;
};
```
---
## Backend Extension Points
### 9. Before Request Middleware
**What:** Intercept HTTP requests before they reach the handler. Use for auth, logging, request transformation.
**Where configured:** `CopilotRuntime` constructor — `beforeRequestMiddleware`
**Default when not provided:** Requests pass through unchanged.
```typescript
type BeforeRequestMiddleware = (params: {
runtime: CopilotRuntime;
request: Request;
path: string;
}) => MaybePromise<Request | void>;
// Return modified Request, or void to pass through
// Return a Response to short-circuit (e.g., 401)
```
```mermaid
graph LR
REQ["Incoming Request"]
BM["beforeRequestMiddleware"]
HANDLER["Route Handler"]
REJECT["401 / Error Response"]
REQ --> BM
BM -->|pass through| HANDLER
BM -->|reject| REJECT
```
---
### 10. After Request Middleware
**What:** Run code after the response is prepared. Use for logging, metrics, cleanup.
**Where configured:** `CopilotRuntime` constructor — `afterRequestMiddleware`
**Default when not provided:** No post-processing.
```typescript
type AfterRequestMiddleware = (params: {
runtime: CopilotRuntime;
response: Response;
path: string;
}) => MaybePromise<void>;
```
---
### 11. Agent Runner
**What:** Controls how agents are executed and how thread state is managed.
**Where configured:** `CopilotRuntime` constructor — `runner`
**Default when not provided:** `InMemoryAgentRunner` — in-process, ephemeral (threads lost on restart).
```typescript
abstract class AgentRunner {
abstract run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
abstract connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
abstract isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
abstract stop(request: AgentRunnerStopRequest): Promise<boolean | undefined>;
}
```
| Implementation | Storage | Persistence | Use case |
| --------------------- | ----------- | ----------- | -------------------------------- |
| `InMemoryAgentRunner` | RAM | No | Development, stateless apps |
| `SQLiteAgentRunner` | Disk | Yes | Production, long-running threads |
| Custom | Your choice | Your choice | Redis, PostgreSQL, etc. |
```mermaid
graph TB
RT["CopilotRuntime"]
RUNNER["runner (AgentRunner)"]
RT --> RUNNER
subgraph Implementations
IM["InMemoryAgentRunner<br/><i>Default — in-process</i>"]
SQ["SQLiteAgentRunner<br/><i>Persistent on disk</i>"]
CU["YourCustomRunner<br/><i>Redis, Postgres, etc.</i>"]
end
RUNNER -.-> IM
RUNNER -.-> SQ
RUNNER -.-> CU
```
---
### 12. Transcription Service
**What:** Convert audio files to text. Enables the `/transcribe` endpoint.
**Where configured:** `CopilotRuntime` constructor — `transcriptionService`
**Default when not provided:** `/transcribe` endpoint returns 404.
```typescript
abstract class TranscriptionService {
abstract transcribeFile(options: {
audioFile: File;
mimeType?: string;
size?: number;
}): Promise<string>;
}
```
---
## Agent-Level Extension Points
### 13. AG-UI Middleware
**What:** Intercept and transform the agent execution pipeline. Cross-cutting concerns like logging, filtering, and backward compatibility.
**Where configured:** At the agent level (outside CopilotKit core).
**Default when not provided:** Direct agent execution.
```typescript
abstract class Middleware {
abstract run(
input: RunAgentInput,
next: AbstractAgent,
): Observable<BaseEvent>;
}
// Built-in implementations:
// - FunctionMiddleware — wrap a function as middleware
// - FilterToolCallsMiddleware — filter which tools are sent
```
```mermaid
graph LR
INPUT["RunAgentInput"]
MW1["Middleware 1<br/><i>e.g., logging</i>"]
MW2["Middleware 2<br/><i>e.g., tool filtering</i>"]
AGENT["Agent.run()"]
INPUT --> MW1 --> MW2 --> AGENT
```
---
## Complete Map: Where Each Extension Plugs In
```mermaid
graph TB
subgraph "Provider / Config"
P["CopilotKitProvider<br/>or provideCopilotKit()"]
P --> FT_P["frontendTools"]
P --> RTC_P["renderToolCalls"]
P --> RAM_P["renderActivityMessages"]
P --> RCM_P["renderCustomMessages"]
P --> HIL_P["humanInTheLoop"]
P --> HDR["headers"]
P --> CRD["credentials"]
P --> PRP["properties"]
P --> DC["showDevConsole"]
end
subgraph "Hooks / Service Methods"
UFT["useFrontendTool()"]
UAC["useAgentContext()"]
URT["useRenderToolCall()"]
UHL["useHumanInTheLoop()"]
UCS["useConfigureSuggestions()"]
URA["useRenderActivityMessage()"]
URC["useRenderCustomMessages()"]
end
subgraph "CopilotRuntime"
RT["new CopilotRuntime()"]
RT --> AGENTS["agents (required)"]
RT --> RUNNER["runner"]
RT --> BM["beforeRequestMiddleware"]
RT --> AM["afterRequestMiddleware"]
RT --> TS["transcriptionService"]
end
subgraph "Core API"
SUB["copilotKit.subscribe()"]
AT["copilotKit.addTool()"]
AC["copilotKit.addContext()"]
end
```
---
## Summary Table
| Extension Point | Location | Config Method | Default | Optional |
| ---------------------------- | -------- | ----------------------------- | ------------------- | -------- |
| **Frontend Tools** | Frontend | Hook / Provider / `addTool()` | None | Yes |
| **Agent Context** | Frontend | Hook / `addContext()` | None | Yes |
| **Tool Call Renderers** | Frontend | Hook / Provider | Generic rendering | Yes |
| **Human-in-the-Loop** | Frontend | Hook / Provider | Immediate execution | Yes |
| **Activity Renderers** | Frontend | Hook / Provider | MCP Apps included | Yes |
| **Custom Message Renderers** | Frontend | Hook / Provider | None | Yes |
| **Suggestions Config** | Frontend | Hook / Config | None | Yes |
| **Event Subscribers** | Frontend | `subscribe()` | None | Yes |
| **Before Middleware** | Backend | Runtime constructor | Pass-through | Yes |
| **After Middleware** | Backend | Runtime constructor | None | Yes |
| **Agent Runner** | Backend | Runtime constructor | InMemoryAgentRunner | Yes |
| **Transcription Service** | Backend | Runtime constructor | None (404) | Yes |
| **AG-UI Middleware** | Agent | Agent-level config | Direct execution | Yes |
+456
View File
@@ -0,0 +1,456 @@
# Angular Setup Guide
This guide shows how to set up CopilotKit in an Angular app — from minimal to fully configured.
---
## What Talks to What
```mermaid
graph LR
subgraph Your Angular App
DI["<b>provideCopilotKit()</b><br/><i>DI token</i>"]
Service["<b>CopilotKit Service</b><br/><i>Injectable</i>"]
Store["<b>AgentStore</b><br/><i>Signal-based state</i>"]
Comp["Your Components"]
end
subgraph Under the Hood
Core["CopilotKitCore<br/><i>Orchestrator</i>"]
Proxy["ProxiedAgent<br/><i>HTTP client</i>"]
end
subgraph Your Server
Runtime["CopilotRuntime<br/><i>Express / Hono</i>"]
end
DI -->|configures| Service
Service -->|wraps| Core
Comp -->|injects| Service
Service -->|creates| Store
Store -->|wraps| Proxy
Proxy -->|HTTP POST + SSE| Runtime
```
---
## Minimal Setup
### 1. Install
```bash
npm install @copilotkit/angular
```
### 2. Configure the DI token
```typescript
// app.config.ts
import { ApplicationConfig } from "@angular/core";
import { provideCopilotKit } from "@copilotkit/angular";
export const appConfig: ApplicationConfig = {
providers: [
provideCopilotKit({
runtimeUrl: "/api/copilotkit",
}),
],
};
```
### 3. Use the service in a component
```typescript
// chat.component.ts
import { Component, inject } from "@angular/core";
import { CopilotKit } from "@copilotkit/angular";
@Component({
selector: "app-chat",
template: `
<div>
<div *ngFor="let msg of agentStore.messages()">
<b>{{ msg.role }}:</b> {{ msg.content }}
</div>
<input #input (keydown.enter)="send(input.value); input.value = ''" />
</div>
`,
})
export class ChatComponent {
private copilotKit = inject(CopilotKit);
agentStore = this.copilotKit.getAgentStore(); // default agent
async send(message: string) {
this.agentStore.addMessage({
id: crypto.randomUUID(),
role: "user",
content: message,
});
await this.copilotKit.runAgent({ agent: this.agentStore.agent });
}
}
```
That's it — the DI token creates a `CopilotKit` service backed by `CopilotKitCore`, and `AgentStore` gives you signal-based reactive state.
```mermaid
sequenceDiagram
participant Config as app.config.ts
participant Service as CopilotKit Service
participant Core as CopilotKitCore
participant Runtime as Your Server
Config->>Service: provideCopilotKit({ runtimeUrl })
Service->>Core: new CopilotKitCore(config)
Core->>Runtime: GET /info
Runtime-->>Core: Available agents
Note over Service: Ready — inject anywhere
```
---
## Angular Signals for Reactive State
`AgentStore` uses Angular signals, so your templates react to changes automatically:
```typescript
@Component({
template: `
@if (agentStore.isRunning()) {
<p>Agent is thinking...</p>
}
@for (msg of agentStore.messages(); track msg.id) {
<div [class]="msg.role">{{ msg.content }}</div>
}
<pre>{{ agentStore.state() | json }}</pre>
`,
})
export class ChatComponent {
private copilotKit = inject(CopilotKit);
agentStore = this.copilotKit.getAgentStore("my-agent");
}
```
### AgentStore Signals
| Signal | Type | What it tracks |
| ------------- | ----------- | -------------------------------------- |
| `messages()` | `Message[]` | All messages in the conversation |
| `isRunning()` | `boolean` | Whether the agent is currently running |
| `state()` | `any` | Agent state (arbitrary JSON) |
```mermaid
graph TB
subgraph AgentStore
Agent["AbstractAgent<br/><i>Subscribed to events</i>"]
MS["messages()<br/><i>Signal&lt;Message[]&gt;</i>"]
IR["isRunning()<br/><i>Signal&lt;boolean&gt;</i>"]
ST["state()<br/><i>Signal&lt;any&gt;</i>"]
end
Agent -->|onMessagesChanged| MS
Agent -->|onRunStarted/Finished| IR
Agent -->|onStateChanged| ST
subgraph Template
T["Your template auto-updates"]
end
MS --> T
IR --> T
ST --> T
```
---
## Registering Tools
```typescript
// In your component or service
import { CopilotKit } from "@copilotkit/angular";
import { z } from "zod";
@Component({
/* ... */
})
export class ProductComponent implements OnInit, OnDestroy {
private copilotKit = inject(CopilotKit);
ngOnInit() {
// Register a tool the agent can call
this.copilotKit.addTool({
name: "addToCart",
description: "Add a product to cart",
parameters: z.object({
productId: z.string(),
quantity: z.number().default(1),
}),
handler: async ({ productId, quantity }) => {
this.cartService.add(productId, quantity);
return `Added ${quantity} item(s)`;
},
});
}
ngOnDestroy() {
// Clean up when component is destroyed
this.copilotKit.removeTool("addToCart");
}
}
```
---
## Providing Context
```typescript
@Component({
/* ... */
})
export class DashboardComponent implements OnInit, OnDestroy {
private copilotKit = inject(CopilotKit);
private contextId?: string;
ngOnInit() {
this.contextId = this.copilotKit.addContext({
description: "Current dashboard metrics",
value: JSON.stringify({
revenue: this.metricsService.revenue(),
activeUsers: this.metricsService.activeUsers(),
}),
});
}
ngOnDestroy() {
if (this.contextId) {
this.copilotKit.removeContext(this.contextId);
}
}
}
```
---
## Tool Call Rendering
Angular uses the `AngularToolCall` type for rendering tool calls:
```typescript
import { AngularToolCall } from "@copilotkit/angular";
// Configure in provideCopilotKit
provideCopilotKit({
runtimeUrl: "/api/copilotkit",
renderToolCalls: [
{
name: "searchProducts",
// The Angular component receives the AngularToolCall
},
],
});
```
### AngularToolCall Status Flow
```mermaid
graph LR
IP["in-progress<br/><i>Args still streaming</i>"]
EX["executing<br/><i>Handler is running</i>"]
CO["complete<br/><i>Result ready</i>"]
IP --> EX --> CO
```
| Field | Type | Description |
| -------- | -------------------------------------------- | ---------------------------------------- |
| `status` | `"in-progress" \| "executing" \| "complete"` | Current lifecycle stage |
| `name` | `string` | Tool name |
| `args` | `Partial<T>` or `T` | Tool arguments (partial while streaming) |
| `result` | `string \| undefined` | Result (only when complete) |
---
## All Configuration Options
```typescript
// app.config.ts
import { provideCopilotKit } from "@copilotkit/angular";
provideCopilotKit({
// Required
runtimeUrl: "/api/copilotkit",
// Authentication
headers: { Authorization: "Bearer token" },
// Custom properties forwarded to agents
properties: { userId: "123", plan: "pro" },
// Local agents for development
agents: { test: myTestAgent },
// Tools (can also add via service)
tools: [
{
name: "myTool",
parameters: z.object({ input: z.string() }),
handler: async ({ input }) => `Processed: ${input}`,
},
],
// Tool call rendering
renderToolCalls: [
/* ... */
],
// Frontend tools
frontendTools: [
/* ... */
],
// Human-in-the-loop
humanInTheLoop: [
/* ... */
],
});
```
```mermaid
graph TB
subgraph "provideCopilotKit() Config"
direction TB
subgraph Required
URL["runtimeUrl"]
end
subgraph "Optional: Auth"
H["headers"]
end
subgraph "Optional: Tools & Rendering"
T["tools"]
FT["frontendTools"]
RTC["renderToolCalls"]
HIL["humanInTheLoop"]
end
subgraph "Optional: Other"
P["properties"]
AG["agents"]
end
end
```
---
## Full Example: Dashboard App
```typescript
// app.config.ts
import { ApplicationConfig } from "@angular/core";
import { provideCopilotKit } from "@copilotkit/angular";
export const appConfig: ApplicationConfig = {
providers: [
provideCopilotKit({
runtimeUrl: "/api/copilotkit",
headers: { Authorization: `Bearer ${getToken()}` },
}),
],
};
```
```typescript
// dashboard.component.ts
import { Component, inject, OnInit, OnDestroy } from "@angular/core";
import { CopilotKit } from "@copilotkit/angular";
import { z } from "zod";
@Component({
selector: "app-dashboard",
template: `
<div class="dashboard">
<app-metrics />
<div class="chat">
@if (agentStore.isRunning()) {
<div class="typing">Agent is thinking...</div>
}
@for (msg of agentStore.messages(); track msg.id) {
<div [class]="'message ' + msg.role">
{{ msg.content }}
</div>
}
<input
#input
placeholder="Ask about your metrics..."
(keydown.enter)="send(input.value); input.value = ''"
/>
</div>
</div>
`,
})
export class DashboardComponent implements OnInit, OnDestroy {
private copilotKit = inject(CopilotKit);
private metricsService = inject(MetricsService);
agentStore = this.copilotKit.getAgentStore();
private contextId?: string;
ngOnInit() {
// Provide context
this.contextId = this.copilotKit.addContext({
description: "Dashboard metrics",
value: JSON.stringify({
revenue: this.metricsService.revenue(),
users: this.metricsService.activeUsers(),
}),
});
// Register tool
this.copilotKit.addTool({
name: "filterMetrics",
description: "Filter dashboard metrics by date range",
parameters: z.object({
startDate: z.string(),
endDate: z.string(),
}),
handler: async ({ startDate, endDate }) => {
this.metricsService.setDateRange(startDate, endDate);
return `Filtered to ${startDate} - ${endDate}`;
},
});
}
ngOnDestroy() {
if (this.contextId) this.copilotKit.removeContext(this.contextId);
this.copilotKit.removeTool("filterMetrics");
}
async send(message: string) {
this.agentStore.addMessage({
id: crypto.randomUUID(),
role: "user",
content: message,
});
await this.copilotKit.runAgent({ agent: this.agentStore.agent });
}
}
```
---
## Key Differences from React
| Aspect | React | Angular |
| ----------------- | ----------------------------------------- | --------------------------------------------------- |
| Configuration | `<CopilotKitProvider>` JSX | `provideCopilotKit()` DI token |
| Service access | `useCopilotKit()` hook | `inject(CopilotKit)` |
| Agent state | `useAgent()` hook returns reactive values | `AgentStore` with Angular signals |
| Tool registration | `useFrontendTool()` hook (auto-cleanup) | `addTool()` / `removeTool()` (manual cleanup) |
| Context | `useAgentContext()` hook (auto-cleanup) | `addContext()` / `removeContext()` (manual cleanup) |
| Reactivity | React re-renders on state change | Angular signals trigger change detection |
+238
View File
@@ -0,0 +1,238 @@
# Intelligence Setup Guide
This guide shows how to set up **CopilotKit Intelligence**: durable thread storage plus a websocket transport for realtime events.
Intelligence is designed to feel like a small runtime configuration change, not a separate product integration. You provide an Intelligence platform client to the runtime, and the rest of the stack switches from plain SSE mode into Intelligence mode automatically.
---
## What Changes in Intelligence Mode
```mermaid
graph TB
subgraph Frontend
App["React / Angular / Vanilla"]
Core["CopilotKitCore"]
Proxy["ProxiedCopilotRuntimeAgent"]
IA["IntelligenceAgent<br/><i>chosen after /info</i>"]
end
subgraph Your Server
RT["CopilotRuntime"]
CPK-I["CopilotKitIntelligence"]
Runner["IntelligenceAgentRunner"]
end
subgraph Intelligence
API["Thread API<br/><i>durable storage</i>"]
WS["Realtime WebSocket"]
end
App --> Core
Core --> Proxy
Proxy -->|info handshake| RT
RT --> CPK-I
CPK-I --> API
RT --> Runner
Runner --> WS
Proxy --> IA
IA -->|REST bootstrap| RT
IA -->|WebSocket events| WS
```
### SSE Mode vs Intelligence Mode
| Mode | Thread storage | Realtime transport | `/info` reports |
| ------------ | ------------------------------------------- | ------------------ | --------------------------------------------- |
| SSE | Ephemeral unless your runner persists state | SSE | `mode: "sse"` |
| Intelligence | Durable thread APIs | WebSocket | `mode: "intelligence"` + `intelligence.wsUrl` |
The important design rule is:
- The **runtime** decides the mode.
- The **client** waits for `/info` before choosing the concrete remote agent implementation.
- The **developer** only opts in by providing `intelligence`.
---
## Minimal Runtime Setup
### 1. Install runtime packages
```bash
npm install @copilotkit/runtime
```
### 2. Create the Intelligence platform client
```typescript
import { CopilotKitIntelligence } from "@copilotkit/runtime";
const intelligence = new CopilotKitIntelligence({
apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!,
organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORGANIZATION_ID!,
apiUrl: "https://your-intelligence-host/api",
wsUrl: "wss://your-intelligence-host/socket",
});
```
### 3. Pass it to `CopilotRuntime`
```typescript
import express from "express";
import { CopilotRuntime } from "@copilotkit/runtime";
import { createCopilotEndpointExpress } from "@copilotkit/runtime/express";
const app = express();
const runtime = new CopilotRuntime({
agents: {
default: myAgent,
},
intelligence,
});
app.use(
"/api/copilotkit",
createCopilotEndpointExpress({
runtime,
basePath: "/",
}),
);
```
That is the mode switch. You do **not** separately configure Intelligence handlers in the endpoint layer. The runtime selects them.
---
## What `CopilotRuntime` Does For You
When `intelligence` is present, `CopilotRuntime`:
- switches its mode from `"sse"` to `"intelligence"`
- uses the Intelligence handler path for `run`, `connect`, and `threads`
- auto-configures the Intelligence runner from `intelligence.wsUrl`
- reports Intelligence metadata from `/info`
Example `/info` response:
```json
{
"version": "1.x.x",
"mode": "intelligence",
"agents": {
"default": {
"name": "default",
"description": "My agent",
"className": "BuiltInAgent"
}
},
"audioFileTranscriptionEnabled": false,
"a2uiEnabled": false,
"intelligence": {
"wsUrl": "wss://your-intelligence-host/socket"
}
}
```
The frontend uses that response to decide whether to keep using the HTTP/SSE path or switch to the Intelligence websocket path.
---
## Frontend Behavior
You do not configure a special provider flag for Intelligence.
This stays the same:
```tsx
import { CopilotKitProvider, CopilotChat } from "@copilotkit/react-core/v2";
export function App() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit">
<CopilotChat />
</CopilotKitProvider>
);
}
```
What changes under the hood:
1. The provider connects to the runtime as usual.
2. `CopilotKitCore` fetches `/info`.
3. `ProxiedCopilotRuntimeAgent` waits until the runtime reports its mode.
4. If the mode is:
- `"sse"`: normal HTTP/SSE behavior continues.
- `"intelligence"`: the proxy uses `IntelligenceAgent` and the runtime-provided websocket URL.
This is why the runtime owns the mode decision instead of the frontend guessing from config.
---
## Durable Threads
Intelligence mode adds thread APIs on the runtime:
| Route | Method | Purpose |
| ---------------------------- | ------ | ------------------------------------ |
| `/threads` | GET | List durable threads |
| `/threads/subscribe` | POST | Get credentials for realtime updates |
| `/threads/:threadId` | PATCH | Update thread metadata |
| `/threads/:threadId/archive` | POST | Archive a thread |
| `/threads/:threadId` | DELETE | Delete a thread |
These routes are **Intelligence-only**.
In SSE mode they should reject with an explicit error, because SSE runtimes do not have the durable thread backend required to satisfy them.
---
## How Agent Runs Work in Intelligence Mode
```mermaid
sequenceDiagram
participant Client as Frontend
participant Runtime as CopilotRuntime
participant CPK-I as CopilotKitIntelligence
participant WS as Intelligence WebSocket
Client->>Runtime: GET /info
Runtime-->>Client: { mode: "intelligence", wsUrl: ... }
Client->>Runtime: POST /agent/default/run
Runtime->>CPK-I: ensure thread exists + acquire lock
CPK-I-->>Runtime: join token / join code
Runtime-->>Client: bootstrap response
Client->>WS: join thread channel
WS-->>Client: AG-UI events in realtime
```
The runtime is still the contract boundary the frontend talks to. Intelligence is not exposed as a separate frontend integration surface.
---
## Local Agents vs Runtime-Discovered Agents
Local or self-managed agents still matter in Intelligence mode.
The intended precedence is:
1. local/self-managed agents
2. runtime-discovered remote agents
That lets application code override a runtime-reported agent with a local implementation for development, testing, or custom routing behavior.
---
## Recommended Mental Model
Think of Intelligence as a **runtime capability**, not a second transport API developers need to learn.
- `CopilotKitIntelligence` configures the runtime's Intelligence backend.
- `CopilotRuntime` exposes that capability through the same frontend-facing contract.
- `/info` tells the client which concrete remote-agent implementation to use.
- Frontend app code stays mostly unchanged.
If the setup feels bigger than “add the CPK-I to the runtime,” the abstraction is probably leaking.
+448
View File
@@ -0,0 +1,448 @@
# React Setup Guide
This guide shows how to set up CopilotKit in a React app — from minimal to fully configured.
---
## What Talks to What
```mermaid
graph LR
subgraph Your React App
Provider["<b>CopilotKitProvider</b><br/><i>Wraps your app</i>"]
Chat["<b>CopilotChat</b><br/><i>Chat UI component</i>"]
Hook1["useFrontendTool()"]
Hook2["useAgentContext()"]
Hook3["useAgent()"]
end
subgraph Under the Hood
Core["CopilotKitCore<br/><i>Orchestrator</i>"]
Proxy["ProxiedAgent<br/><i>HTTP client</i>"]
end
subgraph Your Server
Runtime["CopilotRuntime<br/><i>Express / Hono</i>"]
end
Provider -->|creates| Core
Chat -->|uses| Hook3
Hook1 -->|registers tool in| Core
Hook2 -->|registers context in| Core
Hook3 -->|gets agent from| Core
Core -->|creates| Proxy
Proxy -->|HTTP POST + SSE| Runtime
```
---
## Minimal Setup (V1 — recommended starting point)
### 1. Install
```bash
npm install @copilotkit/react-core @copilotkit/react-ui
```
### 2. Wrap your app with the provider
```tsx
// app.tsx
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";
export default function App() {
return (
<CopilotKit runtimeUrl="/api/copilotkit">
<YourApp />
</CopilotKit>
);
}
```
### 3. Add a chat component
```tsx
// components/chat.tsx
import { CopilotPopup } from "@copilotkit/react-ui";
export function ChatWidget() {
return (
<CopilotPopup
labels={{ title: "AI Assistant", initial: "How can I help?" }}
/>
);
}
```
That's it — you now have a working AI chat. The provider connects to your runtime, fetches available agents, and the popup gives users a chat interface.
```mermaid
sequenceDiagram
participant App as React App
participant Provider as CopilotKit Provider
participant Runtime as Your Server
App->>Provider: Mounts with runtimeUrl
Provider->>Runtime: GET /info
Runtime-->>Provider: Available agents
Note over Provider: Ready for chat
```
---
## V2 Setup (direct)
If you're building new features and want the V2 API directly:
```bash
npm install @copilotkit/react
```
```tsx
import { CopilotKitProvider, CopilotChat } from "@copilotkit/react-core";
export default function App() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit">
<CopilotChat />
</CopilotKitProvider>
);
}
```
> V1's `<CopilotKit>` wraps V2's `<CopilotKitProvider>` under the hood, so both work the same way.
---
## Adding Tools
Tools are functions the AI agent can call. They run in the browser.
```tsx
import { useFrontendTool } from "@copilotkit/react-core";
// or: import { useCopilotAction } from "@copilotkit/react-core"; (V1 equivalent)
import { z } from "zod";
function ProductPage({ products }) {
// The agent can now call "addToCart" during a conversation
useFrontendTool({
name: "addToCart",
description: "Add a product to the user's shopping cart",
parameters: z.object({
productId: z.string().describe("The product ID to add"),
quantity: z.number().default(1).describe("How many to add"),
}),
handler: async ({ productId, quantity }) => {
await cartApi.add(productId, quantity);
return `Added ${quantity} item(s) to cart`;
},
});
return <div>{/* your product UI */}</div>;
}
```
```mermaid
sequenceDiagram
participant Agent as AI Agent
participant Runtime as CopilotRuntime
participant Core as CopilotKitCore
participant Tool as addToCart handler
Agent->>Runtime: TOOL_CALL_START { name: "addToCart" }
Agent->>Runtime: TOOL_CALL_ARGS { productId: "abc", quantity: 2 }
Runtime->>Core: SSE events
Core->>Tool: Execute handler({ productId: "abc", quantity: 2 })
Tool-->>Core: "Added 2 item(s) to cart"
Core->>Runtime: TOOL_CALL_RESULT
Runtime->>Agent: Agent continues with result
```
---
## Providing Context
Context tells the agent about what the user currently sees.
```tsx
import { useAgentContext } from "@copilotkit/react-core";
// or: import { useCopilotReadable } from "@copilotkit/react-core"; (V1 equivalent)
function Dashboard({ user, metrics }) {
// The agent now knows about the current user and their metrics
useAgentContext("Current user and dashboard metrics", {
user: { name: user.name, role: user.role },
metrics: { revenue: metrics.revenue, activeUsers: metrics.activeUsers },
});
return <div>{/* your dashboard UI */}</div>;
}
```
---
## Custom Tool Rendering
Show custom UI while a tool is being called:
```tsx
import { useRenderToolCall } from "@copilotkit/react-core";
import { z } from "zod";
function App() {
useRenderToolCall({
name: "searchProducts",
args: z.object({ query: z.string() }),
render: ({ args, status, result }) => {
if (status === "in-progress") {
return <div>Searching for "{args.query}"...</div>;
}
if (status === "executing") {
return <Spinner>Running search...</Spinner>;
}
// status === "complete"
return <div>Found results: {result}</div>;
},
});
}
```
```mermaid
graph LR
subgraph Tool Call Lifecycle
IP["in-progress<br/><i>Args streaming in</i>"]
EX["executing<br/><i>Handler running</i>"]
CO["complete<br/><i>Result available</i>"]
IP --> EX --> CO
end
```
---
## Human-in-the-Loop
Require user approval before a tool executes:
```tsx
import { useHumanInTheLoop } from "@copilotkit/react-core";
import { z } from "zod";
function App() {
useHumanInTheLoop({
name: "deleteAccount",
description: "Permanently delete a user account",
parameters: z.object({ userId: z.string() }),
render: ({ args, status, respond }) => {
if (status === "executing") {
return (
<div>
<p>Delete account {args.userId}?</p>
<button onClick={() => respond("approved")}>Approve</button>
<button onClick={() => respond("denied")}>Deny</button>
</div>
);
}
if (status === "complete") {
return <div>Action completed</div>;
}
return <div>Preparing...</div>;
},
});
}
```
---
## Suggestions
Auto-generate prompt suggestions for users:
```tsx
import { useConfigureSuggestions } from "@copilotkit/react-core";
function App() {
useConfigureSuggestions({
instructions: "Suggest questions about the user's dashboard data",
minSuggestions: 2,
maxSuggestions: 4,
available: "always", // "before-first-message" | "after-first-message" | "always" | "disabled"
});
}
```
---
## All Provider Props (optional)
```tsx
<CopilotKitProvider
// Required
runtimeUrl="/api/copilotkit"
// Authentication
headers={{ Authorization: "Bearer token" }}
credentials="include" // Forward cookies
publicApiKey="ck_..." // CopilotKit Cloud key
// Custom properties forwarded to agents
properties={{ userId: "123", plan: "pro" }}
// Tools & rendering (can also use hooks instead)
frontendTools={
[
/* ... */
]
}
renderToolCalls={
[
/* ... */
]
}
renderActivityMessages={
[
/* ... */
]
}
renderCustomMessages={
[
/* ... */
]
}
humanInTheLoop={
[
/* ... */
]
}
// Dev tools
showDevConsole="auto" // true | false | "auto"
// Advanced: local agents for development
agents__unsafe_dev_only={{ test: myTestAgent }}
/>
```
```mermaid
graph TB
subgraph "CopilotKitProvider Props"
direction TB
subgraph Required
URL["runtimeUrl"]
end
subgraph "Optional: Auth"
H["headers"]
C["credentials"]
K["publicApiKey"]
end
subgraph "Optional: Tools & Rendering"
FT["frontendTools"]
RTC["renderToolCalls"]
RAM["renderActivityMessages"]
RCM["renderCustomMessages"]
HIL["humanInTheLoop"]
end
subgraph "Optional: Other"
P["properties"]
DC["showDevConsole"]
AG["agents__unsafe_dev_only"]
end
end
```
---
## Chat Component Variants
```tsx
import {
CopilotChat, // Inline chat, fills its container
CopilotPopup, // Floating popup button + chat
CopilotSidebar, // Side panel
CopilotPanel, // Inline panel
} from "@copilotkit/react-ui";
// All accept the same core props:
<CopilotChat
agentId="research" // Which agent to talk to (default: "default")
labels={{
title: "Research Assistant",
initial: "What would you like to research?",
placeholder: "Ask me anything...",
}}
/>;
```
---
## Full Example: E-Commerce App
```tsx
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
import { z } from "zod";
export default function App() {
return (
<CopilotKit
runtimeUrl="/api/copilotkit"
headers={{ Authorization: `Bearer ${getToken()}` }}
>
<CopilotSidebar labels={{ title: "Shopping Assistant" }}>
<ProductCatalog />
</CopilotSidebar>
</CopilotKit>
);
}
function ProductCatalog() {
const [products] = useProducts();
const [cart, setCart] = useCart();
// Context: tell the agent what the user sees
useAgentContext("Product catalog the user is browsing", {
products: products.map((p) => ({ id: p.id, name: p.name, price: p.price })),
cartTotal: cart.total,
cartItems: cart.items.length,
});
// Tool: agent can add items to cart
useFrontendTool({
name: "addToCart",
description: "Add a product to the shopping cart",
parameters: z.object({
productId: z.string(),
quantity: z.number().default(1),
}),
handler: async ({ productId, quantity }) => {
setCart((prev) => addItem(prev, productId, quantity));
return "Added to cart";
},
});
// Tool: agent can search products
useFrontendTool({
name: "searchProducts",
description: "Search for products by name or category",
parameters: z.object({ query: z.string() }),
handler: async ({ query }) => {
const results = products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase()),
);
return JSON.stringify(
results.map((p) => ({ id: p.id, name: p.name, price: p.price })),
);
},
});
// Suggestions
useConfigureSuggestions({
instructions:
"Suggest shopping-related questions based on the product catalog",
maxSuggestions: 3,
available: "always",
});
return <div>{/* product grid UI */}</div>;
}
```
+493
View File
@@ -0,0 +1,493 @@
# Runtime / Backend Setup Guide
This guide shows how to set up the CopilotKit backend — from minimal to fully configured with all optional extension points.
---
## What Talks to What
```mermaid
graph TB
subgraph Frontends
React["React App"]
Angular["Angular App"]
Vanilla["Vanilla JS"]
end
subgraph Your Server
EP["Express / Hono<br/><i>Endpoint handler</i>"]
BM["beforeRequestMiddleware<br/><i>(optional)</i>"]
RT["<b>CopilotRuntime</b>"]
AM["afterRequestMiddleware<br/><i>(optional)</i>"]
Runner["<b>AgentRunner</b><br/><i>InMemory (default)<br/>or SQLite</i>"]
TS["TranscriptionService<br/><i>(optional)</i>"]
end
subgraph Agents
A1["Agent 1<br/><i>LangGraph</i>"]
A2["Agent 2<br/><i>CrewAI</i>"]
A3["Agent 3<br/><i>Custom</i>"]
end
React -->|HTTP| EP
Angular -->|HTTP| EP
Vanilla -->|HTTP| EP
EP --> BM
BM --> RT
RT --> AM
RT --> Runner
RT --> TS
Runner -->|AG-UI events| A1
Runner -->|AG-UI events| A2
Runner -->|AG-UI events| A3
```
---
## Minimal Setup (Express)
### 1. Install
```bash
npm install @copilotkit/runtime express
```
### 2. Create the runtime
```typescript
// server.ts
import express from "express";
import { CopilotRuntime } from "@copilotkit/runtime";
import { createCopilotEndpointExpress } from "@copilotkit/runtime/express";
const app = express();
const runtime = new CopilotRuntime({
agents: {
default: myAgent, // Any AbstractAgent implementation
},
});
app.use("/api/copilotkit", createCopilotEndpointExpress({ runtime }));
app.listen(3000);
```
That's it. The endpoint handler creates these routes automatically:
| Route | Method | What it does |
| ----------------------------------------------- | ------ | ----------------------------------- |
| `/api/copilotkit/info` | GET | Returns list of available agents |
| `/api/copilotkit/agent/:agentId/run` | POST | Run an agent (returns SSE stream) |
| `/api/copilotkit/agent/:agentId/connect` | POST | Connect/reconnect to a thread |
| `/api/copilotkit/agent/:agentId/stop/:threadId` | POST | Stop a running agent |
| `/api/copilotkit/transcribe` | POST | Audio transcription (if configured) |
```mermaid
sequenceDiagram
participant Client as Frontend
participant EP as Express Router
participant RT as CopilotRuntime
participant Agent as AI Agent
Client->>EP: GET /api/copilotkit/info
EP->>RT: List agents
RT-->>Client: [{ id: "default", description: "..." }]
Client->>EP: POST /agent/default/run
EP->>RT: handleRunAgent({ agentId: "default" })
RT->>Agent: runner.run()
Agent-->>Client: SSE: TEXT_MESSAGE_START
Agent-->>Client: SSE: TEXT_MESSAGE_CONTENT
Agent-->>Client: SSE: TEXT_MESSAGE_END
Agent-->>Client: SSE: RUN_FINISHED
```
---
## Hono Setup
```typescript
import { Hono } from "hono";
import { CopilotRuntime } from "@copilotkit/runtime";
import { createCopilotEndpointHono } from "@copilotkit/runtime/hono";
const app = new Hono();
const runtime = new CopilotRuntime({
agents: { default: myAgent },
});
app.route("/api/copilotkit", createCopilotEndpointHono({ runtime }));
export default app;
```
---
## Using the Built-in Agent
CopilotKit includes a built-in agent powered by the Vercel AI SDK:
```typescript
import { CopilotRuntime } from "@copilotkit/runtime";
import { BuiltInAgent } from "@copilotkit/runtime/v2";
const agent = new BuiltInAgent({
model: "openai/gpt-4o",
systemPrompt: "You are a helpful shopping assistant.",
});
const runtime = new CopilotRuntime({
agents: { default: agent },
});
```
---
## Lazy-Loaded Agents
Agents can be a `Promise` — useful for dynamic loading:
```typescript
const runtime = new CopilotRuntime({
agents: loadAgents(), // Returns Promise<Record<string, AbstractAgent>>
});
async function loadAgents() {
const config = await fetchConfig();
return {
default: new BuiltInAgent({ model: config.model }),
research: new HttpAgent({ url: config.researchAgentUrl }),
};
}
```
---
## All CopilotRuntime Options
```typescript
const runtime = new CopilotRuntime({
// Required: map of agent IDs to agent instances
agents: {
default: defaultAgent,
research: researchAgent,
coding: codingAgent,
},
// Optional: how agents are executed
runner: new InMemoryAgentRunner(), // default
// Optional: audio → text
transcriptionService: myTranscriptionService,
// Optional: intercept requests before processing
beforeRequestMiddleware: async ({ request, path }) => {
console.log(`[${path}] Request received`);
// Return modified request, or void to pass through
},
// Optional: run after response is prepared
afterRequestMiddleware: async ({ response, path }) => {
console.log(`[${path}] Response sent`);
},
});
```
```mermaid
graph TB
subgraph "CopilotRuntime Options"
direction TB
subgraph "Required"
AGENTS["agents<br/><i>Record&lt;string, AbstractAgent&gt;</i>"]
end
subgraph "Optional"
RUNNER["runner<br/><i>AgentRunner</i><br/><i>Default: InMemoryAgentRunner</i>"]
TS["transcriptionService<br/><i>TranscriptionService</i>"]
BM["beforeRequestMiddleware<br/><i>(request, path) → Request | void</i>"]
AM["afterRequestMiddleware<br/><i>(response, path) → void</i>"]
end
end
```
---
## AgentRunner: How Agents Execute
The `AgentRunner` is the abstraction that actually executes agents. It manages threads, streaming, and agent lifecycle.
```mermaid
graph TB
subgraph "AgentRunner (Abstract)"
RUN["run(request)<br/><i>Execute agent, return Observable&lt;BaseEvent&gt;</i>"]
CONNECT["connect(request)<br/><i>Reconnect to existing thread</i>"]
RUNNING["isRunning(request)<br/><i>Check if thread is active</i>"]
STOP["stop(request)<br/><i>Abort a running thread</i>"]
end
subgraph Implementations
IM["<b>InMemoryAgentRunner</b><br/><i>Default — in-process, ephemeral</i>"]
SQ["<b>SQLiteAgentRunner</b><br/><i>Persistent state on disk</i>"]
CU["<b>Your Custom Runner</b><br/><i>Extend AgentRunner</i>"]
end
IM --> RUN
SQ --> RUN
CU --> RUN
```
### InMemoryAgentRunner (default)
Stores agent threads in memory. Simple, no persistence. Threads are lost on server restart.
```typescript
import { InMemoryAgentRunner } from "@copilotkit/runtime";
const runtime = new CopilotRuntime({
agents: { default: myAgent },
runner: new InMemoryAgentRunner(), // This is the default
});
```
### SQLiteAgentRunner (persistent)
Stores agent threads in SQLite. Survives restarts.
```typescript
import { SQLiteAgentRunner } from "@copilotkit/sqlite-runner";
const runtime = new CopilotRuntime({
agents: { default: myAgent },
runner: new SQLiteAgentRunner({ dbPath: "./agent-state.db" }),
});
```
### Custom Runner
```typescript
import { AgentRunner } from "@copilotkit/runtime";
import { Observable } from "rxjs";
class RedisAgentRunner extends AgentRunner {
async run(request) {
// Store in Redis, return event stream
return new Observable((subscriber) => {
// ... your implementation
});
}
async connect(request) {
// Reconnect to existing Redis-stored thread
}
async isRunning(request) {
// Check Redis for active thread
}
async stop(request) {
// Signal thread to stop
}
}
```
---
## Middleware
Middleware lets you intercept requests before and after processing.
### Before Request Middleware
Runs before any handler. Use it for auth, logging, request modification.
```typescript
const runtime = new CopilotRuntime({
agents: { default: myAgent },
beforeRequestMiddleware: async ({ request, path, runtime }) => {
// Example: verify auth token
const token = request.headers.get("authorization");
if (!token) {
return new Response("Unauthorized", { status: 401 });
}
// Example: add user context to request
const user = await verifyToken(token);
request.headers.set("x-user-id", user.id);
// Return modified request (or void to pass through)
return request;
},
});
```
### After Request Middleware
Runs after the response is prepared but before it's sent.
```typescript
const runtime = new CopilotRuntime({
agents: { default: myAgent },
afterRequestMiddleware: async ({ response, path, runtime }) => {
// Example: log responses
console.log(`[${path}] Response status: ${response.status}`);
// Example: add custom headers
// (Note: response may be SSE stream)
},
});
```
```mermaid
sequenceDiagram
participant Client
participant Before as beforeRequestMiddleware
participant Handler as Route Handler
participant After as afterRequestMiddleware
Client->>Before: HTTP Request
alt Middleware rejects
Before-->>Client: 401 Unauthorized
else Middleware passes
Before->>Handler: (modified) Request
Handler->>After: Response
After-->>Client: Final Response
end
```
---
## Transcription Service
Enable audio-to-text transcription:
```typescript
import { TranscriptionService } from "@copilotkit/runtime";
class OpenAITranscription extends TranscriptionService {
async transcribeFile({ audioFile, mimeType, size }) {
const formData = new FormData();
formData.append("file", audioFile);
formData.append("model", "whisper-1");
const response = await fetch(
"https://api.openai.com/v1/audio/transcriptions",
{
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: formData,
},
);
const result = await response.json();
return result.text;
}
}
const runtime = new CopilotRuntime({
agents: { default: myAgent },
transcriptionService: new OpenAITranscription(),
});
```
The `/transcribe` endpoint is only active when `transcriptionService` is configured.
---
## Request Flow Detail
```mermaid
sequenceDiagram
participant Client as Frontend
participant CORS as CORS Handler
participant Before as Before Middleware
participant Router as Route Handler
participant Runtime as CopilotRuntime
participant Runner as AgentRunner
participant Agent as AI Agent
participant After as After Middleware
Client->>CORS: POST /agent/default/run
CORS->>Before: Check middleware
Before->>Router: Forward request
Router->>Runtime: handleRunAgent()
Note over Runtime: 1. Resolve agents (await if Promise)
Note over Runtime: 2. Find agent by ID
Note over Runtime: 3. Clone agent (avoid shared state)
Note over Runtime: 4. Parse RunAgentInput from body
Note over Runtime: 5. Set messages, state, threadId
Runtime->>Runner: runner.run({ agent, input })
Runner->>Agent: agent.runAgent(input)
loop SSE Stream
Agent-->>Client: event: TEXT_MESSAGE_CONTENT
end
Agent-->>Client: event: RUN_FINISHED
Router->>After: Response complete
```
---
## Full Example: Express + Multiple Agents + Middleware
```typescript
import express from "express";
import { CopilotRuntime } from "@copilotkit/runtime";
import { createCopilotEndpointExpress } from "@copilotkit/runtime/express";
import { BuiltInAgent } from "@copilotkit/runtime/v2";
import { SQLiteAgentRunner } from "@copilotkit/sqlite-runner";
const app = express();
// Create agents
const generalAgent = new BuiltInAgent({
model: "openai/gpt-4o",
systemPrompt: "You are a helpful assistant.",
});
const codeAgent = new BuiltInAgent({
model: "openai/gpt-4o",
systemPrompt: "You are a coding expert. Always provide code examples.",
});
// Create runtime with all optional features
const runtime = new CopilotRuntime({
agents: {
default: generalAgent,
coding: codeAgent,
},
// Persistent agent state
runner: new SQLiteAgentRunner({ dbPath: "./data/agents.db" }),
// Auth middleware
beforeRequestMiddleware: async ({ request, path }) => {
const token = request.headers.get("authorization")?.replace("Bearer ", "");
if (!token) {
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
// Validate token...
},
// Logging middleware
afterRequestMiddleware: async ({ path }) => {
console.log(`[CopilotKit] ${new Date().toISOString()} ${path}`);
},
});
// Mount CopilotKit endpoints
app.use("/api/copilotkit", createCopilotEndpointExpress({ runtime }));
app.listen(3000, () => {
console.log("Server running on :3000");
console.log("CopilotKit endpoints at /api/copilotkit/*");
});
```
+535
View File
@@ -0,0 +1,535 @@
# Vanilla JavaScript Setup Guide
This guide shows how to use CopilotKit without React or Angular — using the core API directly. This works with any framework (Vue, Svelte, vanilla JS, Node.js, etc).
---
## What Talks to What
```mermaid
graph LR
subgraph Your Code
App["Your Application"]
Sub["Event Subscribers"]
end
subgraph CopilotKit Core
Core["<b>CopilotKitCore</b><br/><i>Orchestrator</i>"]
AR["AgentRegistry"]
RH["RunHandler"]
CS["ContextStore"]
end
subgraph Transport
Proxy["ProxiedAgent<br/><i>HTTP + SSE</i>"]
end
subgraph Your Server
Runtime["CopilotRuntime"]
end
App -->|creates| Core
App -->|subscribes| Sub
Sub -->|listens to| Core
Core --> AR
Core --> RH
Core --> CS
Core -->|creates| Proxy
Proxy -->|HTTP POST + SSE| Runtime
```
---
## Minimal Setup
### 1. Install
```bash
npm install @copilotkit/core
```
### 2. Create the core instance
```typescript
import { CopilotKitCore } from "@copilotkit/core";
const copilotKit = new CopilotKitCore({
runtimeUrl: "http://localhost:3000/api/copilotkit",
});
```
### 3. Get an agent and send a message
```typescript
// Wait for runtime connection
const subscription = copilotKit.subscribe({
onRuntimeConnectionStatusChanged: async ({ status }) => {
if (status === "connected") {
// Agents are now available
const agent = copilotKit.getAgent("default");
// Add a user message
agent.addMessage({
id: crypto.randomUUID(),
role: "user",
content: "Hello, what can you do?",
});
// Run the agent
await copilotKit.runAgent({ agent });
}
},
});
```
That's it — `CopilotKitCore` handles connecting to the runtime, fetching agents, and managing the event lifecycle.
```mermaid
sequenceDiagram
participant App as Your Code
participant Core as CopilotKitCore
participant Runtime as CopilotRuntime
App->>Core: new CopilotKitCore({ runtimeUrl })
Core->>Runtime: GET /info
Runtime-->>Core: Available agents
Core->>App: onRuntimeConnectionStatusChanged("connected")
App->>Core: getAgent("default")
App->>Core: runAgent({ agent })
Core->>Runtime: POST /agent/default/run
Runtime-->>Core: SSE events stream
Core->>App: Messages update via subscription
```
---
## Subscribing to Events
The core provides a rich subscription system — this is how you react to changes without a framework:
```typescript
const subscription = copilotKit.subscribe({
// Connection lifecycle
onRuntimeConnectionStatusChanged: ({ status }) => {
// "disconnected" | "connecting" | "connected" | "error"
updateConnectionUI(status);
},
// Agent availability
onAgentsChanged: ({ agents }) => {
console.log("Available agents:", Object.keys(agents));
// agents is Record<string, AbstractAgent>
},
// Tool execution
onToolExecutionStart: ({ toolName, args, agentId }) => {
showToolSpinner(toolName);
},
onToolExecutionEnd: ({ toolName, result, error }) => {
hideToolSpinner(toolName);
if (error) showError(error);
},
// Context changes
onContextChanged: ({ context }) => {
console.log("Context updated:", context);
},
// Suggestions
onSuggestionsChanged: ({ agentId, suggestions }) => {
renderSuggestionChips(suggestions);
},
// Errors
onError: ({ error, code, context }) => {
// code: "AGENT_CONNECT_FAILED" | "AGENT_RUN_FAILED" |
// "TOOL_HANDLER_FAILED" | "TOOL_ARGUMENT_PARSE_FAILED" |
// "RUNTIME_INFO_FETCH_FAILED"
console.error(`[${code}]`, error.message, context);
},
});
// Clean up when done
subscription.unsubscribe();
```
```mermaid
graph TB
subgraph "CopilotKitCore Events"
direction TB
subgraph Connection
RCS["onRuntimeConnectionStatusChanged"]
end
subgraph Agents
AC["onAgentsChanged"]
end
subgraph "Tool Execution"
TES["onToolExecutionStart"]
TEE["onToolExecutionEnd"]
end
subgraph Context
CC["onContextChanged"]
end
subgraph Suggestions
SC["onSuggestionsChanged"]
SSL["onSuggestionsStartedLoading"]
SFL["onSuggestionsFinishedLoading"]
end
subgraph Errors
ERR["onError"]
end
end
Your["Your subscriber"] --> RCS
Your --> AC
Your --> TES
Your --> TEE
Your --> CC
Your --> SC
Your --> ERR
```
---
## Subscribing to Agent Messages
In addition to core events, you can subscribe directly to an agent's events:
```typescript
const agent = copilotKit.getAgent("default");
const agentSub = agent.subscribe({
// Messages changed (streaming text, new messages, etc.)
onMessagesChanged: ({ messages }) => {
renderChatMessages(messages);
},
// Agent state changed
onStateChanged: ({ state }) => {
updateStateDisplay(state);
},
// Run lifecycle
onRunInitialized: () => {
showTypingIndicator();
},
onRunFinalized: () => {
hideTypingIndicator();
},
onRunFailed: ({ error }) => {
showError(error);
},
// Granular event tracking
onToolCallStartEvent: ({ event }) => {
console.log("Tool call:", event.name);
},
onToolCallEndEvent: ({ toolCallArgs }) => {
console.log("Tool args:", toolCallArgs);
},
onToolCallResultEvent: ({ event }) => {
console.log("Tool result:", event.result);
},
});
// Clean up
agentSub.unsubscribe();
```
---
## Registering Tools
```typescript
import { z } from "zod";
// Add a tool
copilotKit.addTool({
name: "getWeather",
description: "Get current weather for a location",
parameters: z.object({
city: z.string().describe("City name"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
handler: async ({ city, unit }) => {
const data = await fetch(`/api/weather?city=${city}&unit=${unit}`);
return await data.text();
},
followUp: true, // Agent will continue after getting the result
});
// Remove a tool
copilotKit.removeTool("getWeather");
```
### Agent-Specific Tools
```typescript
// This tool is only available to the "research" agent
copilotKit.addTool({
name: "searchPapers",
description: "Search academic papers",
agentId: "research", // Only this agent can call it
parameters: z.object({ query: z.string() }),
handler: async ({ query }) => {
return await searchPapers(query);
},
});
```
---
## Providing Context
```typescript
// Add context (returns an ID for later removal)
const contextId = copilotKit.addContext({
description: "Current user session",
value: JSON.stringify({
userId: "user_123",
role: "admin",
currentPage: "/dashboard",
}),
});
// Update context (remove + re-add)
copilotKit.removeContext(contextId);
const newContextId = copilotKit.addContext({
description: "Current user session",
value: JSON.stringify({
userId: "user_123",
role: "admin",
currentPage: "/settings",
}),
});
// Remove when no longer relevant
copilotKit.removeContext(newContextId);
```
---
## Constructor Options
```typescript
const copilotKit = new CopilotKitCore({
// Required
runtimeUrl: "http://localhost:3000/api/copilotkit",
// Authentication
headers: { Authorization: "Bearer my-token" },
credentials: "include", // Forward cookies
// Runtime transport mode
runtimeTransport: "rest", // "rest" (default) or "single"
// Custom properties forwarded to agents
properties: {
userId: "user_123",
environment: "production",
},
// Initial tools
tools: [
{
name: "myTool",
parameters: z.object({ input: z.string() }),
handler: async ({ input }) => `Result: ${input}`,
},
],
// Local agents (dev only — normally fetched from runtime)
agents__unsafe_dev_only: {
test: myLocalAgent,
},
});
```
```mermaid
graph TB
subgraph "CopilotKitCore Config"
direction TB
subgraph Required
URL["runtimeUrl"]
end
subgraph "Optional: Auth"
H["headers"]
C["credentials"]
end
subgraph "Optional: Transport"
RT["runtimeTransport<br/><i>'rest' or 'single'</i>"]
end
subgraph "Optional: Data"
P["properties"]
T["tools"]
SC["suggestionsConfig"]
end
subgraph "Optional: Dev"
AG["agents__unsafe_dev_only"]
end
end
```
---
## Using HttpAgent Directly (No CopilotKit)
For the simplest possible setup, you can skip CopilotKit entirely and use AG-UI's `HttpAgent` directly:
```typescript
import { HttpAgent } from "@ag-ui/client";
const agent = new HttpAgent({
agentId: "my-agent",
url: "http://localhost:3000/api/copilotkit/agent/my-agent/run",
headers: { Authorization: "Bearer token" },
});
// Subscribe to messages
agent.subscribe({
onMessagesChanged: ({ messages }) => {
console.log("Messages:", messages);
},
});
// Send a message and run
agent.addMessage({
id: crypto.randomUUID(),
role: "user",
content: "Hello!",
});
await agent.runAgent();
```
> **When to use this:** Only if you want zero abstraction and just need to talk to a single agent. You lose tools, context, suggestions, and multi-agent orchestration.
```mermaid
graph LR
subgraph "HttpAgent (Minimal)"
HA["HttpAgent"]
end
subgraph "CopilotKitCore (Full)"
CKC["CopilotKitCore"]
Tools["Tool Registry"]
Context["Context Store"]
Suggestions["Suggestions"]
Multi["Multi-Agent"]
CKC --> Tools
CKC --> Context
CKC --> Suggestions
CKC --> Multi
end
HA -->|HTTP + SSE| Server["Your Server"]
CKC -->|HTTP + SSE| Server
```
---
## Full Example: Simple Chat App (No Framework)
```typescript
import { CopilotKitCore } from "@copilotkit/core";
import { z } from "zod";
// DOM elements
const messagesDiv = document.getElementById("messages")!;
const input = document.getElementById("input") as HTMLInputElement;
const sendBtn = document.getElementById("send")!;
const statusSpan = document.getElementById("status")!;
// Initialize CopilotKit
const copilotKit = new CopilotKitCore({
runtimeUrl: "/api/copilotkit",
});
let currentAgent: any = null;
// Subscribe to core events
copilotKit.subscribe({
onRuntimeConnectionStatusChanged: ({ status }) => {
statusSpan.textContent = status;
if (status === "connected") {
currentAgent = copilotKit.getAgent("default");
setupAgentSubscription();
input.disabled = false;
}
},
onToolExecutionStart: ({ toolName }) => {
appendMessage("system", `Running tool: ${toolName}...`);
},
onError: ({ error, code }) => {
appendMessage("error", `[${code}] ${error.message}`);
},
});
// Subscribe to agent messages
function setupAgentSubscription() {
currentAgent.subscribe({
onMessagesChanged: ({ messages }) => {
messagesDiv.innerHTML = "";
messages.forEach((msg) => appendMessage(msg.role, msg.content));
},
});
}
// Register a tool
copilotKit.addTool({
name: "getCurrentTime",
description: "Get the current date and time",
parameters: z.object({}),
handler: async () => new Date().toISOString(),
});
// Send message
async function sendMessage() {
const text = input.value.trim();
if (!text || !currentAgent) return;
input.value = "";
currentAgent.addMessage({
id: crypto.randomUUID(),
role: "user",
content: text,
});
await copilotKit.runAgent({ agent: currentAgent });
}
sendBtn.addEventListener("click", sendMessage);
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") sendMessage();
});
// Helper
function appendMessage(role: string, content: string) {
const div = document.createElement("div");
div.className = `message ${role}`;
div.textContent = `${role}: ${content}`;
messagesDiv.appendChild(div);
}
```
```html
<!-- index.html -->
<div id="app">
<div>Status: <span id="status">connecting...</span></div>
<div id="messages"></div>
<input id="input" disabled placeholder="Connecting..." />
<button id="send">Send</button>
</div>
<script type="module" src="./main.ts"></script>
```