mirror of
https://github.com/hanzoai/agents.git
synced 2026-10-30 18:48:36 +00:00
8.7 KiB
8.7 KiB
Chat History Provider
A vendor-agnostic interface for retrieving chat history from AI coding assistants.
Overview
IChatHistoryProvider abstracts chat history retrieval across different coding agents (Claude Code, Cursor, Codex, etc.). Write your code once against the interface, and it works with any supported agent.
┌─────────────────────────────────────────────────────────┐
│ Your Application │
│ │ │
│ IChatHistoryProvider │
│ │ │
├─────────────┬─────────────┼─────────────┬───────────────┤
│ ClaudeCode │ Cursor │ Codex │ Factory │
│ Agent │ Agent │ Agent │ Agent │
└─────────────┴─────────────┴─────────────┴───────────────┘
Vendor-Agnostic Usage
Basic Pattern
import type { IChatHistoryProvider } from '../interfaces';
// Accept any provider - doesn't matter which agent
async function displayRecentSessions(provider: IChatHistoryProvider) {
const result = await provider.listSessionSummaries({ lookbackDays: 7 });
if (!result.success) {
console.error('Failed:', result.error?.message);
return;
}
for (const session of result.data) {
console.log(`${session.id} - ${session.projectName}`);
console.log(` Messages: ${session.messageCount}, Tools: ${session.toolCallCount}`);
console.log(` First: ${session.firstUserMessage?.substring(0, 50)}...`);
}
}
Working with Multiple Providers
import type { IChatHistoryProvider } from '../interfaces';
import type { SessionSummary } from '../types';
// Aggregate sessions from multiple agents
async function getAllSessions(
providers: IChatHistoryProvider[]
): Promise<SessionSummary[]> {
const allSessions: SessionSummary[] = [];
for (const provider of providers) {
const result = await provider.listSessionSummaries({ lookbackDays: 7 });
if (result.success) {
allSessions.push(...result.data);
}
}
// Sort by timestamp across all providers
return allSessions.sort((a, b) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
);
}
Fetching Full Session Content
async function getSessionMessages(
provider: IChatHistoryProvider,
sessionId: string
) {
const result = await provider.getFilteredSession(sessionId);
if (!result.success || !result.data) {
return null;
}
const session = result.data;
for (const message of session.messages) {
switch (message.messageType) {
case 'user':
console.log(`User: ${message.content}`);
break;
case 'assistant':
console.log(`Assistant: ${message.content}`);
break;
case 'tool_call':
console.log(`Tool: ${message.tool?.name} (${message.tool?.category})`);
break;
case 'thinking':
console.log(`Thinking: ${message.thinking?.content.substring(0, 100)}...`);
break;
}
}
return session;
}
Filtering Messages
By Message Type
// Get only tool calls
const result = await provider.getFilteredSession(sessionId, {
messageTypes: ['tool_call', 'tool_result']
});
// Get only user/assistant conversation (no tools, no thinking)
const result = await provider.getFilteredSession(sessionId, {
messageTypes: ['user', 'assistant']
});
By Role
// Get only user messages
const result = await provider.getFilteredSession(sessionId, {
roles: ['user']
});
By Content
// Search for specific text
const result = await provider.getFilteredSession(sessionId, {
searchText: 'authentication'
});
Session-Level Filtering
// Only sessions with thinking blocks
const result = await provider.listSessionSummaries({
hasThinking: true
});
// Only sessions with 10+ tool calls
const result = await provider.listSessionSummaries({
minToolCallCount: 10
});
// Filter by project
const result = await provider.listSessionSummaries({
projectName: 'my-project'
});
Streaming (Memory Efficient)
For large sessions, use the streaming API to process messages one at a time:
async function processLargeSession(
provider: IChatHistoryProvider,
sessionId: string
) {
const stream = provider.streamSessionMessages?.(sessionId);
if (!stream) {
console.log('Provider does not support streaming');
return;
}
let toolCount = 0;
for await (const message of stream) {
if (message.messageType === 'tool_call') {
toolCount++;
console.log(`Tool ${toolCount}: ${message.tool?.name}`);
}
}
console.log(`Total tools: ${toolCount}`);
}
Incremental Sync
For efficient polling/sync scenarios:
let lastSyncTime = 0;
async function syncNewSessions(provider: IChatHistoryProvider) {
// Get only sessions modified since last sync
const modTimes = await provider.getSessionModificationTimes({
sinceTimestamp: lastSyncTime
});
if (!modTimes.success) return;
for (const [sessionId, mtime] of modTimes.data) {
console.log(`Session ${sessionId} modified at ${new Date(mtime)}`);
// Fetch and process the updated session...
}
lastSyncTime = Date.now();
}
Message Types
| Type | Description |
|---|---|
user |
User input message |
assistant |
Assistant response text |
tool_call |
Tool invocation (Read, Write, Bash, etc.) |
tool_result |
Result from a tool call |
thinking |
Chain-of-thought reasoning |
mcp_tool |
MCP server tool call |
system |
System message |
summary |
Conversation summary |
error |
Error message |
Tool Categories
| Category | Examples |
|---|---|
file_read |
Read, cat, head |
file_write |
Write, Edit |
file_search |
Glob, Grep, find |
shell |
Bash, terminal |
web |
WebFetch, WebSearch |
code_intel |
LSP operations |
mcp |
MCP server tools |
custom |
Unknown/other |
Rich Message Fields
interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: string;
// Rich type information
messageType?: MessageType;
// Tool details (when messageType is 'tool_call' or 'tool_result')
tool?: {
name: string;
category: ToolCategory;
input?: Record<string, unknown>;
output?: string;
status?: 'pending' | 'success' | 'error';
};
// Thinking content (when messageType is 'thinking')
thinking?: {
content: string;
isRedacted?: boolean;
};
// Raw agent data (preserved for debugging)
agentMetadata?: Record<string, unknown>;
}
Creating a Provider Instance
import { ClaudeCodeAgent } from '../implementations/ClaudeCodeAgent';
// Create and initialize
const agent = new ClaudeCodeAgent({});
await agent.initialize();
// Use as IChatHistoryProvider
const sessions = await agent.listSessionSummaries({ lookbackDays: 7 });
Data Paths
Each provider reads from agent-specific locations:
// Get where a provider reads data from
const paths = provider.getDataPaths();
// ClaudeCodeAgent: ['~/.claude/projects']
// CursorAgent: ['~/Library/Application Support/Cursor/...']
Error Handling
All methods return a Result<T, AgentError> type:
const result = await provider.listSessionSummaries();
if (result.success) {
// result.data contains the sessions
console.log(result.data);
} else {
// result.error contains error details
console.error(result.error?.code, result.error?.message);
}
Interface Reference
interface IChatHistoryProvider {
// List sessions with metadata (fast, no full message loading)
listSessionSummaries(filter?: SessionFilterOptions): Promise<Result<SessionSummary[], AgentError>>;
// Get full session with messages
getFilteredSession(sessionId: string, filter?: MessageFilterOptions): Promise<Result<SessionContent | null, AgentError>>;
// Get modification times for sync
getSessionModificationTimes(filter?: SessionFilterOptions): Promise<Result<Map<string, number>, AgentError>>;
// Stream messages (optional, memory efficient)
streamSessionMessages?(sessionId: string, filter?: MessageFilterOptions): AsyncGenerator<ChatMessage>;
// Watch for changes (optional)
watchSessions?(callback: (change: SessionChange) => void): () => void;
// Get data source paths
getDataPaths(): string[];
}