UNPKG

@aichatkit/storage-adapter

Version:
492 lines (360 loc) 12.8 kB
# @aichatkit/storage-adapter Base storage adapter abstract class for Hypermode ChatKit with backend synchronization support and rich response items. ## Installation ```bash npm install @aichatkit/storage-adapter ``` ## Usage 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`. ```typescript 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<void> Saves a conversation with all its response items to storage. ```typescript abstract saveConversation(conversation: Conversation): Promise<void>; ``` **Parameters**: - `conversation`: The conversation object with items array containing messages, tool calls, cards, etc. **Example**: ```typescript 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. ```typescript 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. ```typescript abstract getAllConversations(): Promise<Conversation[]>; ``` **Returns**: Promise resolving to an array of all conversations with their items ### deleteConversation(id: string): Promise<boolean> Deletes a conversation from storage. ```typescript 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. ```typescript abstract addItem(conversationId: string, item: ChatResponseItem): Promise<Conversation | null>; ``` **Parameters**: - `conversationId`: ID of the conversation - `item`: Response item to add (message, tool call, card, etc.) **Returns**: Promise resolving to the updated conversation or null if not found **Example**: ```typescript // 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. ```typescript 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. ```typescript abstract getConversationItems(conversationId: string): Promise<ChatResponseItem[]>; ``` This method should: 1. Check if a backend agent exists for the conversation 2. Sync with backend if available to get all item types 3. Fall back to local storage if backend sync fails ### getConversationHistory(conversationId: string): Promise<Message[]> Gets conversation history (messages only) with backend synchronization. ```typescript async getConversationHistory(conversationId: string): Promise<Message[]> ``` This convenience method filters message items from all response items. ### clearConversationHistory(conversationId: string): Promise<void> Clears conversation history both locally and on backend. ```typescript abstract clearConversationHistory(conversationId: string): Promise<void>; ``` ### setConversationAgent(conversationId: string, agentId: string): Promise<void> Maps a conversation to a backend agent ID. ```typescript abstract setConversationAgent(conversationId: string, agentId: string): Promise<void>; ``` ### getConversationAgent(conversationId: string): Promise<string | null> Gets the backend agent ID for a conversation. ```typescript abstract getConversationAgent(conversationId: string): Promise<string | null>; ``` ## Optional Methods ### initialize(config?: Record<string, any>): Promise<void> Optional initialization method. ```typescript async initialize(config?: Record<string, any>): Promise<void> { return Promise.resolve(); } ``` ### syncAllConversationsWithBackend(): Promise<void> Syncs all conversations with backend (called on app load). ```typescript async syncAllConversationsWithBackend?(): Promise<void> { return Promise.resolve(); } ``` ## Network Callbacks Storage adapters can receive network callbacks for backend synchronization: ### StorageAdapterCallbacks Interface ```typescript interface StorageAdapterCallbacks { getConversationItems?: (agentId: string) => Promise<ChatResponseItem[]> clearConversationHistory?: (agentId: string) => Promise<void> } ``` ### setNetworkCallbacks(callbacks: StorageAdapterCallbacks): void Sets network callbacks for backend communication. ```typescript 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 ```typescript interface Conversation { id: string title: string items: ChatResponseItem[] // Mixed array of messages, tool calls, cards, etc. } ``` ### ChatResponseItem Union type supporting multiple item types: ```typescript 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 ```typescript 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: ```typescript // 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 mapping ``` ## Synchronization Flow 1. **App Initialization**: Call `syncAllConversationsWithBackend()` to get latest state 2. **Conversation Switch**: Sync specific conversation when user switches to it 3. **Item History**: Always try backend first, fall back to local storage 4. **Background Sync**: Periodically sync to keep data fresh ### Example Sync Implementation ```typescript 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](../localstorage-adapter)**: Browser localStorage implementation - **Custom implementations**: Create your own for databases, cloud storage, etc. ## Testing Mock storage adapters for testing: ```typescript 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](../../LICENSE) © Hypermode