npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@aichatkit/storage-adapter

v0.0.0-alpha.6

Published

Base storage adapter for Hypermode ChatKit

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-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.

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 conversation
  • item: 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:

  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.

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 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

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

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