Overview
CopilotKit is a WebSocket-based agent integration pattern that provides real-time, bidirectional communication between your React frontend and Python backend. It’s ideal for building chat interfaces and contextual help features with minimal setup.
When to Use CopilotKit
Best suited for:
Simple chat interfaces with real-time streaming
Contextual help and in-app assistance
Quick Q&A features with instant responses
Real-time collaboration features
Single-agent conversations
Not recommended for:
Complex multi-agent orchestration (use AG-UI Protocol instead)
Workflows requiring persistent state across sessions (use Workspace Context Sync)
Bulk data processing or long-running background tasks
Architecture
CopilotKit uses WebSocket connections for low-latency, bidirectional communication:
Key Benefits:
Low latency : WebSocket maintains persistent connection for instant responses
Bidirectional : Server can push updates to client without polling
Streaming : Real-time token streaming as the AI generates responses
Simple state : Client-side state management with React hooks
Backend Setup
1. Install Dependencies
Add CopilotKit to your Python backend:
pip install copilotkit fastapi
Your requirements.txt should include:
copilotkit>=1.0.0
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
2. Create CopilotKit Endpoint
Create a new file app/api/copilot.py:
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
from copilotkit import CopilotKitSDK, LangGraphAgent
from app.agent.graph import create_agent_graph
from typing import Optional
import logging
logger = logging.getLogger( __name__ )
router = APIRouter()
# Create LangGraph workflow
agent_graph = create_agent_graph()
# Initialize CopilotKit SDK with LangGraph agent
sdk = CopilotKitSDK(
agents = [
LangGraphAgent(
name = "opshub-nav-agent" ,
description = "AI assistant for investment operations and portfolio analysis" ,
graph = agent_graph,
),
]
)
@router.websocket ( "/copilotkit/ws" )
async def copilot_websocket (
websocket : WebSocket,
api_key : Optional[ str ] = Query( None , description = "API key for authentication" )
):
"""
WebSocket endpoint for CopilotKit bidirectional communication.
Handles:
- Real-time state synchronization
- Streaming agent responses
- Tool execution events
- API key authentication via query parameter
"""
# Verify API key
if not verify_api_key(api_key):
await websocket.close( code = 1008 , reason = "Invalid or missing API key" )
return
await websocket.accept()
logger.info( "CopilotKit WebSocket connection accepted" )
try :
# Let CopilotKit SDK handle the WebSocket communication
await sdk.handle_websocket(websocket)
except WebSocketDisconnect:
logger.info( "CopilotKit WebSocket disconnected normally" )
except Exception as e:
logger.error( f "CopilotKit WebSocket error: { e } " , exc_info = True )
await websocket.close( code = 1011 , reason = "Internal server error" )
3. Register the Router
In your app/main.py:
from fastapi import FastAPI
from app.api.copilot import router as copilot_router
app = FastAPI()
# Include CopilotKit router
app.include_router(copilot_router)
4. Health Check Endpoint
Add a health check to verify the CopilotKit service:
@router.get ( "/copilotkit/health" )
async def copilot_health ():
"""Health check for CopilotKit endpoint."""
return {
"status" : "healthy" ,
"service" : "CopilotKit Runtime" ,
"agents" : [
{ "name" : agent.name, "description" : agent.description}
for agent in sdk.agents
],
"features" : [
"WebSocket bidirectional communication" ,
"LangGraph agent integration" ,
"Real-time state synchronization" ,
"Tool execution streaming"
]
}
Frontend Setup
1. Install Dependencies
The CopilotKit packages are already included in OpsHub’s package.json:
{
"@copilotkit/react-core" : "^1.10.6" ,
"@copilotkit/react-ui" : "^1.10.6" ,
"@copilotkit/runtime-client-gql" : "^1.10.6"
}
2. Create API Proxy Route
Create app/api/copilotkit/route.ts to proxy WebSocket connections:
Next.js API Route
WebSocket Upgrade
import { buildAgentUrl } from '@/lib/config/agent-backend-url' ;
import { getAuthHeaders } from '@/lib/api/backend-auth' ;
import { NextRequest } from 'next/server' ;
export async function POST ( request : NextRequest ) {
// Get authentication headers with JWT token
const authHeaders = await getAuthHeaders ();
// Build backend URL
const backendUrl = buildAgentUrl ( '/copilotkit/ws' );
// Proxy request to Python backend
const response = await fetch ( backendUrl , {
method: 'POST' ,
headers: {
... authHeaders ,
'Content-Type' : 'application/json' ,
},
body: await request . text (),
});
return response ;
}
// For WebSocket upgrade handling
export const runtime = 'edge' ;
export async function GET ( request : NextRequest ) {
const authHeaders = await getAuthHeaders ();
const backendUrl = buildAgentUrl ( '/copilotkit/ws' );
// Upgrade to WebSocket connection
return fetch ( backendUrl , {
headers: {
... authHeaders ,
Upgrade: 'websocket' ,
Connection: 'Upgrade' ,
},
});
}
3. Add CopilotKit Provider
Wrap your page with the CopilotKit provider:
// app/(app)/your-page/page.tsx
import { CopilotKit } from "@copilotkit/react-core" ;
import { CopilotPopup } from "@copilotkit/react-ui" ;
import "@copilotkit/react-ui/styles.css" ;
export default function YourPage () {
return (
< CopilotKit runtimeUrl = "/api/copilotkit" >
< YourComponent />
{ /* Add floating chat popup */ }
< CopilotPopup
labels = {{
title : "Portfolio Assistant" ,
initial : "Hi! How can I help with your portfolio today?" ,
}}
/>
</ CopilotKit >
);
}
4. Use CopilotKit Hooks
Access chat functionality in your components:
import { useCopilotChat } from "@copilotkit/react-core" ;
export function ChatComponent () {
const {
messages ,
sendMessage ,
isLoading ,
error ,
} = useCopilotChat ();
const handleSend = async ( text : string ) => {
await sendMessage ({
role: "user" ,
content: text ,
});
};
return (
< div >
{ messages . map (( msg , i ) => (
< div key = { i } className = {msg. role } >
{ msg . content }
</ div >
))}
{ isLoading && < div > Agent is thinking ...</ div >}
{ error && < div > Error : { error . message }</ div >}
< input
onKeyPress = {(e) => {
if ( e . key === 'Enter' ) {
handleSend ( e . currentTarget . value );
}
}}
placeholder = "Ask me anything..."
/>
</ div >
);
}
Complete Example
Here’s a full working example of a contextual help sidebar:
// app/(app)/portfolio/page.tsx
import { CopilotKit } from "@copilotkit/react-core" ;
import { CopilotSidebar } from "@copilotkit/react-ui" ;
import "@copilotkit/react-ui/styles.css" ;
import { PortfolioTable } from "@/components/portfolio/table" ;
export default function PortfolioPage () {
return (
< CopilotKit runtimeUrl = "/api/copilotkit" >
< div className = "flex h-screen" >
{ /* Main content */ }
< div className = "flex-1" >
< h1 > Portfolio Holdings </ h1 >
< PortfolioTable />
</ div >
{ /* Contextual sidebar */ }
< CopilotSidebar
labels = {{
title : "Portfolio Assistant" ,
initial : "I can help you analyze holdings, run validations, or answer questions about your portfolio." ,
}}
defaultOpen = { false }
/>
</ div >
</ CopilotKit >
);
}
Authentication
CopilotKit uses JWT tokens for secure authentication:
Server-Side (Next.js API Route)
import { getAuthHeaders } from '@/lib/api/backend-auth' ;
export async function POST ( request : Request ) {
// Get JWT token from Supabase session
const headers = await getAuthHeaders ();
// headers = { 'Authorization': 'Bearer <jwt_token>' }
const response = await fetch ( backendUrl , {
headers ,
// ... rest of config
});
return response ;
}
Backend (Python FastAPI)
from fastapi import WebSocket, Query, HTTPException
from typing import Optional
async def verify_api_key ( api_key : Optional[ str ]) -> bool :
"""Verify JWT token or API key."""
if not api_key:
return False
# Validate JWT token with Supabase
# Or check against configured API key
return api_key == settings. AGENT_API_KEY
@router.websocket ( "/copilotkit/ws" )
async def copilot_websocket (
websocket : WebSocket,
api_key : Optional[ str ] = Query( None )
):
if not await verify_api_key(api_key):
await websocket.close( code = 1008 , reason = "Invalid API key" )
return
await websocket.accept()
await sdk.handle_websocket(websocket)
Always use JWT tokens from Supabase sessions instead of user IDs. The getAuthHeaders() utility automatically extracts the JWT token from the session.
Error Handling
Connection Failures
Handle WebSocket connection failures gracefully:
import { CopilotKit } from "@copilotkit/react-core" ;
import { useState } from "react" ;
export function RobustCopilotProvider ({ children }) {
const [ connectionError , setConnectionError ] = useState < string | null >( null );
return (
< CopilotKit
runtimeUrl = "/api/copilotkit"
onError = {(error) => {
console . error ( "CopilotKit error:" , error );
setConnectionError ( error . message );
}}
>
{ connectionError && (
< div className = "bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded" >
Connection error : { connectionError }
</ div >
)}
{ children }
</ CopilotKit >
);
}
Reconnection Logic
The WebSocket connection automatically reconnects on disconnect:
import { useCopilotChat } from "@copilotkit/react-core" ;
import { useEffect } from "react" ;
export function ChatWithReconnect () {
const { connectionState , reconnect } = useCopilotChat ();
useEffect (() => {
if ( connectionState === "disconnected" ) {
// Attempt reconnection after 3 seconds
const timer = setTimeout (() => {
reconnect ();
}, 3000 );
return () => clearTimeout ( timer );
}
}, [ connectionState , reconnect ]);
return (
< div >
{ connectionState === " disconnected " && (
< div className = "text-yellow-600" >
Connection lost . Reconnecting ...
</ div >
)}
{ /* Rest of chat UI */ }
</ div >
);
}
Backend Error Handling
@router.websocket ( "/copilotkit/ws" )
async def copilot_websocket ( websocket : WebSocket):
try :
await websocket.accept()
await sdk.handle_websocket(websocket)
except WebSocketDisconnect:
logger.info( "Client disconnected normally" )
except ValueError as e:
logger.error( f "Invalid data: { e } " )
await websocket.close( code = 1007 , reason = "Invalid data format" )
except Exception as e:
logger.error( f "Unexpected error: { e } " , exc_info = True )
await websocket.close( code = 1011 , reason = "Internal server error" )
Best Practices
Performance Optimize for low latency:
Keep messages small (avoid sending large JSON payloads)
Use message batching for multiple rapid updates
Implement debouncing for user input
Close connections when not in use
User Experience Provide clear feedback:
Show typing indicators while agent is thinking
Display connection status in the UI
Handle errors gracefully with user-friendly messages
Provide retry mechanisms for failed messages
Security Always secure your connections:
Use JWT tokens, never expose API keys to the frontend
Validate all user inputs on the backend
Implement rate limiting to prevent abuse
Log all authentication failures
State Management Keep state simple:
CopilotKit handles client-side state automatically
Store conversation history in your database if persistence is needed
Use Supabase for session data (see agent.sessions table)
Clean up old sessions periodically
Troubleshooting
WebSocket Connection Fails
Problem: Connection immediately closes or never establishes.
Solutions:
Check that backend is running: curl http://localhost:8000/copilotkit/health
Verify API proxy route exists: app/api/copilotkit/route.ts
Check browser console for authentication errors
Ensure runtimeUrl prop matches your API route
// Correct
< CopilotKit runtimeUrl = "/api/copilotkit" >
// Incorrect - missing /api prefix
< CopilotKit runtimeUrl = "/copilotkit" >
Authentication Errors
Problem: Backend returns 401 or 1008 WebSocket close code.
Solutions:
Verify Supabase session exists: supabase.auth.getSession()
Check JWT token is being sent: inspect network request headers
Ensure backend API key matches environment variable
Test with the health endpoint first
# Test authentication
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
http://localhost:8000/copilotkit/health
Messages Not Streaming
Problem: Agent responses appear all at once instead of streaming.
Solutions:
Verify backend is using yield for streaming responses
Check Content-Type header is text/event-stream
Ensure proxy is not buffering responses
Test directly with backend (bypass Next.js proxy)
High Latency
Problem: Slow response times or delayed message delivery.
Solutions:
Check network latency: test direct backend connection
Optimize LangGraph workflow (reduce tool calls)
Use faster LLM models (GPT-3.5 instead of GPT-4)
Implement response caching for common queries
WebSocket connections can be blocked by corporate firewalls or proxies. Consider providing an HTTP fallback option for enterprise users.
Next Steps
Set up your backend
Follow the Backend Setup section to create your CopilotKit endpoint
Add the frontend provider
Wrap your page with the CopilotKit provider as shown in Frontend Setup
Test the connection
Use the health endpoint to verify your setup: /api/copilotkit/health