contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
229 lines (228 loc) • 8.66 kB
JavaScript
import { LLMFactory } from "./llm/LLMFactory.js";
import { FileService } from "./fileService.js";
import crypto from 'crypto';
import path from 'path';
export class ConversationManager {
constructor() {
this.conversations = new Map();
this.conversationStoragePath = './.config/conversations';
this.fileService = new FileService();
this.loadPersistedConversations();
}
/**
* Start a new conversation with an agent
*/
async startConversation(agentConfig) {
const conversationId = crypto.randomUUID();
const llm = await LLMFactory.getConfiguredProvider();
if (!llm) {
throw new Error("No LLM provider configured. Please configure an LLM provider first.");
}
const projectContext = await this.loadProjectContext();
const conversation = {
id: conversationId,
agent: agentConfig,
llm: llm,
messages: [],
context: projectContext,
createdAt: Date.now(),
updatedAt: Date.now(),
metadata: {}
};
this.conversations.set(conversationId, conversation);
await this.persistConversation(conversation);
return conversationId;
}
/**
* Continue an existing conversation
*/
async continueConversation(conversationId, message) {
const conversation = this.conversations.get(conversationId);
if (!conversation) {
throw new Error(`Conversation ${conversationId} not found`);
}
// Add user message to history
const userMessage = {
role: 'user',
content: message,
timestamp: Date.now()
};
conversation.messages.push(userMessage);
// Build contextual prompt with conversation history
const contextualPrompt = this.buildContextualPrompt(conversation, message);
try {
// Get response from LLM
const response = await conversation.llm.executePrompt(contextualPrompt, {
systemPrompt: conversation.agent.systemPrompt,
temperature: 0.7,
maxTokens: 2000
});
// Add assistant response to history
const assistantMessage = {
role: 'assistant',
content: response.content,
timestamp: Date.now()
};
conversation.messages.push(assistantMessage);
// Update conversation metadata
conversation.updatedAt = Date.now();
// Persist updated conversation
await this.persistConversation(conversation);
return response.content;
}
catch (error) {
console.error('Error in conversation:', error);
throw new Error(`Failed to get response: ${error.message}`);
}
}
/**
* Get conversation history
*/
getConversation(conversationId) {
return this.conversations.get(conversationId);
}
/**
* List all active conversations
*/
listConversations() {
return Array.from(this.conversations.values());
}
/**
* Delete a conversation
*/
async deleteConversation(conversationId) {
const conversation = this.conversations.get(conversationId);
if (!conversation) {
return false;
}
this.conversations.delete(conversationId);
// Remove persisted conversation file
try {
const conversationPath = path.join(this.conversationStoragePath, `${conversationId}.json`);
await this.fileService.deleteFile(conversationPath);
}
catch (error) {
console.warn(`Failed to delete conversation file: ${error}`);
}
return true;
}
/**
* Build contextual prompt with conversation history and project context
*/
buildContextualPrompt(conversation, currentMessage) {
const { agent, messages, context } = conversation;
let prompt = `You are ${agent.name}, ${agent.systemPrompt}\n\n`;
// Add project context if available
if (context.relevantFiles.length > 0) {
prompt += `Project Context:\n`;
prompt += `Working Directory: ${context.workingDirectory}\n`;
prompt += `Relevant Files: ${context.relevantFiles.join(', ')}\n\n`;
}
// Add conversation history (last 10 messages to avoid token limits)
const recentMessages = messages.slice(-10);
if (recentMessages.length > 0) {
prompt += `Conversation History:\n`;
recentMessages.forEach(msg => {
prompt += `${msg.role}: ${msg.content}\n`;
});
prompt += `\n`;
}
prompt += `Current Message: ${currentMessage}\n\n`;
prompt += `Please respond as ${agent.name} with your expertise in ${agent.expertise?.join(', ') || 'general topics'}.`;
prompt += ` Use a ${agent.writingStyle} writing style with a ${agent.tone} tone.`;
return prompt;
}
/**
* Load project context from current working directory
*/
async loadProjectContext() {
const workingDirectory = process.cwd();
try {
// Get list of relevant files (markdown, text, code files)
const relevantFiles = await this.findRelevantFiles(workingDirectory);
return {
workingDirectory,
relevantFiles,
projectMetadata: {
analyzedAt: Date.now(),
fileCount: relevantFiles.length
},
lastAnalyzed: Date.now()
};
}
catch (error) {
console.warn('Failed to load project context:', error);
return {
workingDirectory,
relevantFiles: [],
projectMetadata: {},
lastAnalyzed: Date.now()
};
}
}
/**
* Find relevant files in the project directory
*/
async findRelevantFiles(directory) {
try {
const files = await this.fileService.listFiles(directory);
// Filter for relevant file types
const relevantExtensions = ['.md', '.txt', '.js', '.ts', '.json', '.py', '.java', '.cpp', '.c', '.h'];
const relevantFiles = files.filter(file => relevantExtensions.some(ext => file.endsWith(ext))).slice(0, 20); // Limit to first 20 files to avoid overwhelming context
return relevantFiles;
}
catch (error) {
console.warn('Failed to find relevant files:', error);
return [];
}
}
/**
* Persist conversation to disk
*/
async persistConversation(conversation) {
try {
const conversationPath = path.join(this.conversationStoragePath, `${conversation.id}.json`);
// Create a serializable version (remove LLM instance)
const serializable = {
...conversation,
llm: conversation.llm.constructor.name // Store just the provider name
};
await this.fileService.saveFile({
path: conversationPath,
content: JSON.stringify(serializable, null, 2)
});
}
catch (error) {
console.warn('Failed to persist conversation:', error);
}
}
/**
* Load persisted conversations from disk
*/
async loadPersistedConversations() {
try {
const conversationFiles = await this.fileService.listFiles(this.conversationStoragePath);
for (const file of conversationFiles) {
if (file.endsWith('.json')) {
try {
const content = await this.fileService.readFile(file);
const conversationData = JSON.parse(content);
// Restore LLM provider
const llm = LLMFactory.getConfiguredProvider();
if (llm) {
conversationData.llm = llm;
this.conversations.set(conversationData.id, conversationData);
}
}
catch (error) {
console.warn(`Failed to load conversation from ${file}:`, error);
}
}
}
}
catch (error) {
// Directory might not exist yet, that's okay
console.debug('No persisted conversations found');
}
}
}