copilotkit-langgraph-history
v0.1.6
Published
Open-source CopilotKit + LangGraph chat history persistence. Restore thread history on page refresh.
Maintainers
Readme
copilotkit-langgraph-history
Open-source LangGraph thread history persistence for CopilotKit. Restore chat history on page refresh with your own LangGraph deployment.
The Problem
Building a chat app with CopilotKit and LangGraph? You've probably noticed:
- Page refresh = empty chat - All messages disappear
- Thread switching loses context - No automatic history restoration
- No persistence of agent state - Users lose context
CopilotKit's default runtime doesn't automatically fetch historical messages from LangGraph's checkpoint system. This package adds that capability.
The Solution
With this package:
- Chat history restored on page load
- Seamless thread switching
- Agent state preserved
- Works with any LangGraph deployment (LangGraph Cloud, self-hosted)
- MIT licensed, open-source
Installation
npm install copilotkit-langgraph-history
# or
pnpm add copilotkit-langgraph-history
# or
yarn add copilotkit-langgraph-historyPeer Dependencies
This package requires the following peer dependencies:
npm install @copilotkit/runtime @copilotkitnext/runtime @ag-ui/core @langchain/langgraph-sdk rxjsQuick Start
Next.js App Router
// app/api/copilotkit/route.ts
import { CopilotRuntime, createCopilotEndpointSingleRoute } from "@copilotkit/runtime/v2";
import {
HistoryHydratingAgentRunner,
createIsolatedAgent,
} from "copilotkit-langgraph-history";
const deploymentUrl = process.env.LANGGRAPH_DEPLOYMENT_URL!;
const langsmithApiKey = process.env.LANGSMITH_API_KEY;
const graphId = "my-agent";
function createRuntime() {
// Create isolated agent (prevents serverless state contamination)
const agent = createIsolatedAgent({
deploymentUrl,
graphId,
langsmithApiKey,
});
// Create history-hydrating runner
const runner = new HistoryHydratingAgentRunner({
agent,
deploymentUrl,
graphId,
langsmithApiKey,
historyLimit: 100, // Max messages to load
});
return new CopilotRuntime({
agents: { [graphId]: agent },
runner,
});
}
export const POST = async (req: Request) => {
const runtime = createRuntime();
const route = createCopilotEndpointSingleRoute({
runtime,
basePath: "/api/copilotkit",
});
return route.handleRequest(req);
};Frontend (React)
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotChat } from "@copilotkit/react-ui";
function App() {
return (
<CopilotKit
runtimeUrl="/api/copilotkit"
agent="my-agent"
threadId={threadId} // Pass your thread ID here
>
<CopilotChat />
</CopilotKit>
);
}Configuration
HistoryHydratingAgentRunner Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| agent | LangGraphAgent | required | The LangGraphAgent instance |
| deploymentUrl | string | required | LangGraph deployment URL |
| graphId | string | required | Graph identifier |
| langsmithApiKey | string | undefined | LangSmith API key |
| historyLimit | number | 100 | Max checkpoints to fetch (max 1000) |
| clientTimeoutMs | number | 1800000 | HTTP timeout (default 30 min) |
| debug | boolean | false | Enable debug logging |
| stateExtractor | function | undefined | Custom state extraction |
createIsolatedAgent Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| deploymentUrl | string | required | LangGraph deployment URL |
| graphId | string | required | Graph identifier |
| langsmithApiKey | string | undefined | LangSmith API key |
| clientTimeoutMs | number | 1800000 | HTTP timeout |
| debug | boolean | false | Enable debug mode |
Advanced Usage
Custom State Extraction
If you need to extract custom fields from the CopilotKit request:
const runner = new HistoryHydratingAgentRunner({
agent,
deploymentUrl,
graphId,
stateExtractor: (input, forwardedProps) => ({
// Extract from forwardedProps.configurable (useCoAgent config)
tenantId: forwardedProps?.configurable?.tenantId as string,
userId: forwardedProps?.configurable?.userId as string,
// Or from input.state (useCoAgent initialState)
...input.state,
}),
});Why createIsolatedAgent?
In serverless environments (especially Vercel Fluid Compute), Node.js module-level state can be shared between bundled routes. This causes a critical bug where the LangGraph deployment URL gets contaminated between different agent configurations.
createIsolatedAgent fixes this by:
- Creating agents with frozen, immutable config
- Verifying the internal client URL matches expected
- Force-replacing the client if contamination is detected
Always use createIsolatedAgent instead of new LangGraphAgent() in serverless environments.
Debug Mode
Enable debug logging to troubleshoot issues:
const runner = new HistoryHydratingAgentRunner({
// ...
debug: true,
});This logs:
- History fetching progress
- Message transformation details
- Stream processing events
- State extraction results
How It Works
History Hydration Flow
When a client connects to an existing thread:
- Fetch History: Retrieves all checkpoints from LangGraph via
client.threads.getHistory() - Extract Messages: Processes checkpoints chronologically, deduplicating messages by ID
- Transform Format: Converts LangGraph messages to CopilotKit format
- Emit Events: Sends
MESSAGES_SNAPSHOTandSTATE_SNAPSHOTevents to frontend - Join Stream: If thread is busy, joins the active execution stream
Event Types Handled
on_chat_model_stream→TEXT_MESSAGE_CONTENTon_chat_model_start→TEXT_MESSAGE_STARTon_chat_model_end→TEXT_MESSAGE_ENDon_tool_start→TOOL_CALL_STARTon_tool_end→TOOL_CALL_END- Custom CopilotKit events (manual message/tool/state emission)
- Interrupt events
API Reference
Exports
// Core
export { HistoryHydratingAgentRunner } from "copilotkit-langgraph-history";
export { createIsolatedAgent } from "copilotkit-langgraph-history";
// Types
export type {
HistoryHydratingRunnerConfig,
StateExtractor,
CreateIsolatedAgentConfig,
LangGraphMessage,
ThreadState,
} from "copilotkit-langgraph-history";
// Constants
export {
DEFAULT_TIMEOUT,
DEFAULT_HISTORY_LIMIT,
MAX_HISTORY_LIMIT,
} from "copilotkit-langgraph-history";
// Event Enums
export {
CustomEventNames,
LangGraphEventTypes,
} from "copilotkit-langgraph-history";
// Utilities (advanced)
export {
transformMessages,
extractContent,
processStreamChunk,
} from "copilotkit-langgraph-history";Environment Variables
# Required
LANGGRAPH_DEPLOYMENT_URL=https://your-deployment.langchain.com
# Optional (for authentication)
LANGSMITH_API_KEY=your-api-keyTroubleshooting
"No history found for thread"
- Ensure the thread exists in LangGraph
- Check that
deploymentUrlis correct - Verify
langsmithApiKeyhas access to the deployment
Messages not loading on refresh
- Confirm
threadIdis being passed to<CopilotKit> - Check browser console for hydration errors
- Enable
debug: trueto see detailed logs
"URL mismatch detected" warning
This is expected when the runner detects and fixes serverless state contamination. The client is automatically replaced with the correct URL.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see LICENSE for details.
Credits
Created by Daniel Frey.
Inspired by the need for thread history persistence in CopilotKit + LangGraph applications.
