UNPKG

mcp-chat-adapter

Version:
219 lines 7.57 kB
import fs from 'fs/promises'; import path from 'path'; import { CONVERSATION_DIR } from './constants.js'; import { ConversationStorageError } from './types.js'; /** * Sleep for a specified number of milliseconds */ export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); /** * Validate create conversation arguments */ export const isValidCreateConversationArgs = (args) => { if (typeof args !== 'object' || args === null) { return false; } const candidate = args; // Check optional string parameters if (candidate.model !== undefined && typeof candidate.model !== 'string') { return false; } if (candidate.system_prompt !== undefined && typeof candidate.system_prompt !== 'string') { return false; } // Check optional parameters object if (candidate.parameters !== undefined) { if (typeof candidate.parameters !== 'object' || candidate.parameters === null) { return false; } const optionalNumericParams = [ 'max_tokens', 'temperature', 'top_p', 'frequency_penalty', 'presence_penalty', ]; for (const param of optionalNumericParams) { if (candidate.parameters[param] !== undefined && typeof candidate.parameters[param] !== 'number') { return false; } } } // Check optional metadata object if (candidate.metadata !== undefined) { if (typeof candidate.metadata !== 'object' || candidate.metadata === null) { return false; } // Check title if present if (candidate.metadata.title !== undefined && typeof candidate.metadata.title !== 'string') { return false; } // Check tags if present if (candidate.metadata.tags !== undefined) { if (!Array.isArray(candidate.metadata.tags)) { return false; } // Ensure all tags are strings if (candidate.metadata.tags.some(tag => typeof tag !== 'string')) { return false; } } } return true; }; /** * Validate chat arguments */ export const isValidChatArgs = (args) => { if (typeof args !== 'object' || args === null) { return false; } const candidate = args; // conversation_id and message are required if (typeof candidate.conversation_id !== 'string') { return false; } if (typeof candidate.message !== 'string') { return false; } // Check optional parameters object if (candidate.parameters !== undefined) { if (typeof candidate.parameters !== 'object' || candidate.parameters === null) { return false; } const optionalNumericParams = [ 'max_tokens', 'temperature', 'top_p', 'frequency_penalty', 'presence_penalty', ]; for (const param of optionalNumericParams) { if (candidate.parameters[param] !== undefined && typeof candidate.parameters[param] !== 'number') { return false; } } } return true; }; /** * Ensure the conversations directory exists */ export const ensureConversationDir = async () => { try { await fs.mkdir(CONVERSATION_DIR, { recursive: true }); } catch (error) { throw new ConversationStorageError(`Failed to create conversations directory: ${error.message}`); } }; /** * Get conversation filename from ID */ export const getConversationFilePath = (conversationId) => { return path.join(CONVERSATION_DIR, `${conversationId}.json`); }; /** * Read conversation from disk */ export const readConversation = async (conversationId) => { try { const filePath = getConversationFilePath(conversationId); const data = await fs.readFile(filePath, 'utf-8'); return JSON.parse(data); } catch (error) { throw new ConversationStorageError(`Failed to read conversation: ${error.message}`); } }; /** * Write conversation to disk */ export const writeConversation = async (conversation) => { try { await ensureConversationDir(); const filePath = getConversationFilePath(conversation.id); const data = JSON.stringify(conversation, null, 2); await fs.writeFile(filePath, data, 'utf-8'); } catch (error) { throw new ConversationStorageError(`Failed to write conversation: ${error.message}`); } }; /** * List all conversations */ export const listConversations = async (log) => { try { await ensureConversationDir(); const files = await fs.readdir(CONVERSATION_DIR); const jsonFiles = files.filter(file => file.endsWith('.json')); const metadataList = []; for (const file of jsonFiles) { try { const conversationId = path.basename(file, '.json'); const conversationData = await readConversation(conversationId); metadataList.push({ id: conversationData.id, model: conversationData.model, created_at: conversationData.created_at, updated_at: conversationData.updated_at, message_count: conversationData.messages.length, metadata: conversationData.metadata }); } catch (error) { log.warn('Storage', `Error reading conversation file ${file}: ${error.message}`); // Skip invalid files } } // Sort by updated_at timestamp, newest first return metadataList.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()); } catch (error) { throw new ConversationStorageError(`Failed to list conversations: ${error.message}`); } }; /** * Delete a conversation */ export const deleteConversation = async (conversationId) => { try { const filePath = getConversationFilePath(conversationId); await fs.unlink(filePath); } catch (error) { throw new ConversationStorageError(`Failed to delete conversation: ${error.message}`); } }; /** * Get the next available conversation ID by finding the highest existing ID and incrementing it * * Using sequential IDs (1, 2, 3, ...) instead of UUIDs makes for a better user experience, * as they are easier to remember, type, and reference. This is especially helpful when users * need to manually specify conversation IDs in the chat tool. */ export const getNextConversationId = async () => { try { await ensureConversationDir(); const files = await fs.readdir(CONVERSATION_DIR); const jsonFiles = files.filter(file => file.endsWith('.json')); let highestId = 0; for (const file of jsonFiles) { const fileBaseName = path.basename(file, '.json'); const idNumber = parseInt(fileBaseName, 10); // Check if the filename is a valid integer if (!isNaN(idNumber) && String(idNumber) === fileBaseName) { highestId = Math.max(highestId, idNumber); } } // Return the next ID as a string return String(highestId + 1); } catch (error) { throw new ConversationStorageError(`Failed to get next conversation ID: ${error.message}`); } }; //# sourceMappingURL=utils.js.map