chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
```
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -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<T> 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 |
|
||||
@@ -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<Message[]></i>"]
|
||||
IR["isRunning()<br/><i>Signal<boolean></i>"]
|
||||
ST["state()<br/><i>Signal<any></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 |
|
||||
@@ -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.
|
||||
@@ -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>;
|
||||
}
|
||||
```
|
||||
@@ -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<string, AbstractAgent></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<BaseEvent></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/*");
|
||||
});
|
||||
```
|
||||
@@ -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>
|
||||
```
|
||||
Reference in New Issue
Block a user