@aichatkit/storage-adapter
v0.0.0-alpha.6
Published
Base storage adapter for Hypermode ChatKit
Maintainers
Readme
@aichatkit/storage-adapter
Base storage adapter abstract class for Hypermode ChatKit with backend synchronization support and rich response items.
Installation
npm install @aichatkit/storage-adapterUsage
This package provides the base abstract class for implementing storage adapters. You typically won't
use this package directly, but rather create or use an implementation like
@aichatkit/localstorage-adapter.
import { StorageAdapter } from "@aichatkit/storage-adapter"
import { Conversation, ChatResponseItem, Message } from "@aichatkit/types"
// Example: Create a custom adapter implementation
class CustomStorageAdapter extends StorageAdapter {
private conversations: Map<string, Conversation> = new Map()
private agentMapping: Map<string, string> = new Map()
async saveConversation(conversation: Conversation): Promise<void> {
this.conversations.set(conversation.id, { ...conversation })
}
async getConversation(id: string): Promise<Conversation | null> {
return this.conversations.get(id) || null
}
async getAllConversations(): Promise<Conversation[]> {
return Array.from(this.conversations.values())
}
async deleteConversation(id: string): Promise<boolean> {
const deleted = this.conversations.delete(id)
this.agentMapping.delete(id)
return deleted
}
async addItem(conversationId: string, item: ChatResponseItem): Promise<Conversation | null> {
const conversation = this.conversations.get(conversationId)
if (!conversation) return null
conversation.items.push(item)
await this.saveConversation(conversation)
return conversation
}
async getConversationItems(conversationId: string): Promise<ChatResponseItem[]> {
// Check if we have network callbacks for backend sync
const agentId = await this.getConversationAgent(conversationId)
if (agentId && this.callbacks?.getConversationItems) {
try {
const backendItems = await this.callbacks.getConversationItems(agentId)
// Update local storage with backend data
const conversation = await this.getConversation(conversationId)
if (conversation) {
conversation.items = backendItems
await this.saveConversation(conversation)
}
return backendItems
} catch (error) {
console.error("Backend sync failed, using local data:", error)
}
}
// Fall back to local data
const conversation = await this.getConversation(conversationId)
return conversation?.items || []
}
async clearConversationHistory(conversationId: string): Promise<void> {
const agentId = await this.getConversationAgent(conversationId)
// Clear on backend if possible
if (agentId && this.callbacks?.clearConversationHistory) {
try {
await this.callbacks.clearConversationHistory(agentId)
} catch (error) {
console.error("Failed to clear backend history:", error)
}
}
// Clear locally
const conversation = await this.getConversation(conversationId)
if (conversation) {
conversation.items = []
await this.saveConversation(conversation)
}
}
async setConversationAgent(conversationId: string, agentId: string): Promise<void> {
this.agentMapping.set(conversationId, agentId)
}
async getConversationAgent(conversationId: string): Promise<string | null> {
return this.agentMapping.get(conversationId) || null
}
}Abstract Methods
All storage adapters must implement these core methods:
saveConversation(conversation: Conversation): Promise
Saves a conversation with all its response items to storage.
abstract saveConversation(conversation: Conversation): Promise<void>;Parameters:
conversation: The conversation object with items array containing messages, tool calls, cards, etc.
Example:
await adapter.saveConversation({
id: "conv-1",
title: "My Chat",
items: [
{
id: "msg_1",
type: "message",
content: "Hello",
role: "user",
},
{
id: "tool_1",
type: "tool_call",
toolCall: {
id: "call_1",
name: "get_weather",
arguments: { location: "NYC" },
status: "completed",
result: { temperature: "22°C" },
},
},
],
})getConversation(id: string): Promise<Conversation | null>
Retrieves a conversation from storage by ID with all its response items.
abstract getConversation(id: string): Promise<Conversation | null>;Parameters:
id: ID of the conversation to retrieve
Returns: Promise resolving to the conversation with all item types or null if not found
getAllConversations(): Promise<Conversation[]>
Retrieves all conversations from storage with their complete response items.
abstract getAllConversations(): Promise<Conversation[]>;Returns: Promise resolving to an array of all conversations with their items
deleteConversation(id: string): Promise
Deletes a conversation from storage.
abstract deleteConversation(id: string): Promise<boolean>;Parameters:
id: ID of the conversation to delete
Returns: Promise resolving to true if successful, false otherwise
addItem(conversationId: string, item: ChatResponseItem): Promise<Conversation | null>
Adds any type of response item to a conversation.
abstract addItem(conversationId: string, item: ChatResponseItem): Promise<Conversation | null>;Parameters:
conversationId: ID of the conversationitem: Response item to add (message, tool call, card, etc.)
Returns: Promise resolving to the updated conversation or null if not found
Example:
// Add a message
await adapter.addItem("conv-1", {
id: "msg_2",
type: "message",
content: "How's the weather?",
role: "user",
})
// Add a tool call
await adapter.addItem("conv-1", {
id: "tool_2",
type: "tool_call",
toolCall: {
id: "call_2",
name: "get_weather",
arguments: { location: "NYC" },
status: "executing",
},
})addMessage(conversationId: string, message: Message): Promise<Conversation | null>
Convenience method for adding message items.
async addMessage(conversationId: string, message: Message): Promise<Conversation | null>This method converts a Message to a MessageItem and calls addItem().
Backend Synchronization Methods
These methods enable synchronization with backend agents:
getConversationItems(conversationId: string): Promise<ChatResponseItem[]>
Gets all conversation items with backend synchronization.
abstract getConversationItems(conversationId: string): Promise<ChatResponseItem[]>;This method should:
- Check if a backend agent exists for the conversation
- Sync with backend if available to get all item types
- Fall back to local storage if backend sync fails
getConversationHistory(conversationId: string): Promise<Message[]>
Gets conversation history (messages only) with backend synchronization.
async getConversationHistory(conversationId: string): Promise<Message[]>This convenience method filters message items from all response items.
clearConversationHistory(conversationId: string): Promise
Clears conversation history both locally and on backend.
abstract clearConversationHistory(conversationId: string): Promise<void>;setConversationAgent(conversationId: string, agentId: string): Promise
Maps a conversation to a backend agent ID.
abstract setConversationAgent(conversationId: string, agentId: string): Promise<void>;getConversationAgent(conversationId: string): Promise<string | null>
Gets the backend agent ID for a conversation.
abstract getConversationAgent(conversationId: string): Promise<string | null>;Optional Methods
initialize(config?: Record<string, any>): Promise
Optional initialization method.
async initialize(config?: Record<string, any>): Promise<void> {
return Promise.resolve();
}syncAllConversationsWithBackend(): Promise
Syncs all conversations with backend (called on app load).
async syncAllConversationsWithBackend?(): Promise<void> {
return Promise.resolve();
}Network Callbacks
Storage adapters can receive network callbacks for backend synchronization:
StorageAdapterCallbacks Interface
interface StorageAdapterCallbacks {
getConversationItems?: (agentId: string) => Promise<ChatResponseItem[]>
clearConversationHistory?: (agentId: string) => Promise<void>
}setNetworkCallbacks(callbacks: StorageAdapterCallbacks): void
Sets network callbacks for backend communication.
import { NetworkAdapter } from "@aichatkit/network-adapter"
const networkAdapter = new SomeNetworkAdapter()
const storageAdapter = new SomeStorageAdapter()
// Connect storage with network for syncing
storageAdapter.setNetworkCallbacks({
getConversationItems: (agentId) => networkAdapter.getConversationItems(agentId),
clearConversationHistory: (agentId) => networkAdapter.clearConversationHistory(agentId),
})Data Types
Conversation
interface Conversation {
id: string
title: string
items: ChatResponseItem[] // Mixed array of messages, tool calls, cards, etc.
}ChatResponseItem
Union type supporting multiple item types:
type ChatResponseItem = MessageItem | ToolCallItem | CardItem
interface MessageItem {
id: string | number
type: "message"
content: string
role: "user" | "assistant"
timestamp?: string
}
interface ToolCallItem {
id: string | number
type: "tool_call"
toolCall: {
id: string
name: string
arguments: Record<string, any>
status: "pending" | "executing" | "completed" | "error"
result?: any
error?: string
}
}
interface CardItem {
id: string | number
type: "card"
card: {
id: string
type: string
title?: string
content: Record<string, any>
actions?: CardAction[]
}
}Message
interface Message {
id: string | number
content: string
role: "user" | "assistant"
timestamp?: string
}Agent-Conversation Mapping
Storage adapters maintain a mapping between conversations and backend agents:
// When creating a new conversation with an agent
await storageAdapter.setConversationAgent("conv-1", "agent-123")
// When retrieving the agent for a conversation
const agentId = await storageAdapter.getConversationAgent("conv-1")
// When deleting a conversation, also remove the agent mapping
await storageAdapter.deleteConversation("conv-1") // Should also remove agent mappingSynchronization Flow
- App Initialization: Call
syncAllConversationsWithBackend()to get latest state - Conversation Switch: Sync specific conversation when user switches to it
- Item History: Always try backend first, fall back to local storage
- Background Sync: Periodically sync to keep data fresh
Example Sync Implementation
async getConversationItems(conversationId: string): Promise<ChatResponseItem[]> {
const agentId = await this.getConversationAgent(conversationId);
if (agentId && this.callbacks?.getConversationItems) {
try {
// Try to get latest from backend
const backendItems = await this.callbacks.getConversationItems(agentId);
// Update local storage with backend data
const conversation = await this.getConversation(conversationId);
if (conversation) {
conversation.items = backendItems;
await this.saveConversation(conversation);
}
return backendItems;
} catch (error) {
console.error('Backend sync failed:', error);
// Continue to local fallback
}
}
// Fall back to local storage
const conversation = await this.getConversation(conversationId);
return conversation?.items || [];
}Available Implementations
- @aichatkit/localstorage-adapter: Browser localStorage implementation
- Custom implementations: Create your own for databases, cloud storage, etc.
Testing
Mock storage adapters for testing:
class MockStorageAdapter extends StorageAdapter {
private data: Map<string, Conversation> = new Map()
async saveConversation(conversation: Conversation): Promise<void> {
this.data.set(conversation.id, { ...conversation })
}
async getConversation(id: string): Promise<Conversation | null> {
return this.data.get(id) || null
}
// ... implement other methods
}License
MIT © Hypermode
