npm package: @macrow/copilotkit-langgraph-history
Description: Open-source CopilotKit + LangGraph chat history persistence. Restore thread history on page refresh.
Author: Daniel Frey
Latest version: 0.1.7

# copilotkit-langgraph-history

This is a temporary fork of xxx.
PR submitted: https://github.com/clickspider/copilotkit-langgraph-history/pull/3

[![npm version](https://img.shields.io/npm/v/copilotkit-langgraph-history.svg)](https://www.npmjs.com/package/copilotkit-langgraph-history)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)

**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](https://copilotkit.ai) and [LangGraph](https://langchain-ai.github.io/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

```bash
npm install copilotkit-langgraph-history
# or
pnpm add copilotkit-langgraph-history
# or
yarn add copilotkit-langgraph-history
```

### Peer Dependencies

This package requires the following peer dependencies:

```bash
npm install @copilotkit/runtime @copilotkitnext/runtime @ag-ui/core @langchain/langgraph-sdk rxjs
```

## Quick Start

### Next.js App Router

```typescript
// 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,
    onRequest: (_url, initReq) => {
      initReq.headers = { ...initReq.headers, Authorization: 'Bearer authToken' };
      return initReq;
    },
  });

  // Create history-hydrating runner
  const runner = new HistoryHydratingAgentRunner({
    agent,
    deploymentUrl,
    graphId,
    langsmithApiKey,
    historyLimit: 100, // Max messages to load
    onRequest: (_url, initReq) => {
      initReq.headers = { ...initReq.headers, Authorization: 'Bearer authToken' };
      return initReq;
    },
  });

  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)

```tsx
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             |
| `onRequest` | `function` | `undefined` | Custom client request hook          |

### `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 |
| `onRequest` | `function` | `undefined` | Custom client request hook |

## Advanced Usage

### Custom State Extraction

If you need to extract custom fields from the CopilotKit request:

```typescript
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:
1. Creating agents with frozen, immutable config
2. Verifying the internal client URL matches expected
3. 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:

```typescript
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:

1. **Fetch History**: Retrieves all checkpoints from LangGraph via `client.threads.getHistory()`
2. **Extract Messages**: Processes checkpoints chronologically, deduplicating messages by ID
3. **Transform Format**: Converts LangGraph messages to CopilotKit format
4. **Emit Events**: Sends `MESSAGES_SNAPSHOT` and `STATE_SNAPSHOT` events to frontend
5. **Join Stream**: If thread is busy, joins the active execution stream

### Event Types Handled

- `on_chat_model_stream` → `TEXT_MESSAGE_CONTENT`
- `on_chat_model_start` → `TEXT_MESSAGE_START`
- `on_chat_model_end` → `TEXT_MESSAGE_END`
- `on_tool_start` → `TOOL_CALL_START`
- `on_tool_end` → `TOOL_CALL_END`
- Custom CopilotKit events (manual message/tool/state emission)
- Interrupt events

## API Reference

### Exports

```typescript
// 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

```env
# Required
LANGGRAPH_DEPLOYMENT_URL=https://your-deployment.langchain.com

# Optional (for authentication)
LANGSMITH_API_KEY=your-api-key
```

## Troubleshooting

### "No history found for thread"

- Ensure the thread exists in LangGraph
- Check that `deploymentUrl` is correct
- Verify `langsmithApiKey` has access to the deployment

### Messages not loading on refresh

- Confirm `threadId` is being passed to `<CopilotKit>`
- Check browser console for hydration errors
- Enable `debug: true` to 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.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

MIT License - see [LICENSE](LICENSE) for details.

## Credits

Created by [Daniel Frey](https://github.com/clickspider).

Inspired by the need for thread history persistence in CopilotKit + LangGraph applications.