UNPKG

@agentics.org/sparc2

Version:

SPARC 2.0 - Autonomous Vector Coding Agent + MCP. SPARC 2.0, vectorized AI code analysis, is an intelligent coding agent framework built to automate and streamline software development. It combines secure execution environments, and version control into a

384 lines (330 loc) 11.5 kB
/** * VectorStore module for SPARC 2.0 * Provides methods to index diff logs and perform vector searches * using OpenAI's vector store API */ import { LogEntry } from "../logger.ts"; import { load } from "https://deno.land/std@0.203.0/dotenv/mod.ts"; // Dynamically import OpenAI to avoid issues in test environments let OpenAI: any; try { OpenAI = (await import("jsr:@openai/openai")).default; } catch (error) { console.debug("OpenAI import failed, using mock implementation", { error: String(error) }); } /** * Diff entry interface for storing code changes */ export interface DiffEntry { id: string; file: string; diff: string; metadata: Record<string, unknown>; } /** * Vector search result interface */ export interface VectorSearchResult { entry: LogEntry | DiffEntry; score: number; } // Store ID for the vector store let vectorStoreId: string | null = null; // Initialize OpenAI client if API key is available let openai: any = null; let apiKey: string | null = null; // Try to load environment variables try { // Only try to load environment variables if not in test mode if (Deno.env.get("DENO_ENV") !== "test") { await load({ export: true, allowEmptyValues: true }); } apiKey = Deno.env.get("OPENAI_API_KEY") || Deno.env.get("VITE_OPENAI_API_KEY") || null; if (apiKey && OpenAI) { openai = new OpenAI({ apiKey, }); console.debug("OpenAI client initialized"); } } catch (error) { console.debug("Failed to load environment variables", { error: String(error) }); } /** * Initialize the vector store * @param name Name for the vector store * @returns The vector store ID */ export async function initializeVectorStore(name: string = "sparc2-vector-store"): Promise<string> { try { // If we already have a vector store ID, return it if (vectorStoreId !== null) { console.debug("Using existing vector store", { id: vectorStoreId }); return vectorStoreId as string; } // If OpenAI client is not available, use mock implementation if (!openai) { vectorStoreId = "mock-vector-store-id"; console.debug("Using mock vector store", { id: vectorStoreId }); return vectorStoreId; } // Create a new vector store const vectorStore = await openai.vectorStores.create({ name }); vectorStoreId = vectorStore.id; console.debug("Vector store initialized", { id: vectorStoreId }); return vectorStoreId as string; } catch (error: unknown) { console.debug("Failed to initialize vector store, using mock", { error: String(error) }); vectorStoreId = "mock-vector-store-id" as string; return vectorStoreId; } } /** * Store a log entry in the vector database * @param entry Log entry to store */ export async function vectorStoreLog(entry: LogEntry): Promise<void> { try { // If OpenAI client is not available, use mock implementation if (!openai) { console.debug("Vector store: Storing log entry (mock)", entry.message, entry.level); return; } // Ensure vector store is initialized const storeId = await initializeVectorStore(); // Create a text file with the log entry const timestamp = new Date().toISOString(); const content = `Log Entry (${timestamp}) Level: ${entry.level} Message: ${entry.message} Metadata: ${JSON.stringify(entry.metadata || {}, null, 2)}`; const file = new File( [content], `log-${Date.now()}.txt`, { type: "text/plain" }, ); // Upload the file to OpenAI const uploadedFile = await openai.files.create({ file, purpose: "assistants", }); // Add the file to the vector store await openai.vectorStores.files.create(storeId, { file_id: uploadedFile.id, }); console.debug("Log entry stored in vector database", { timestamp: entry.timestamp, level: entry.level, message: entry.message.substring(0, 50) + (entry.message.length > 50 ? "..." : ""), }); } catch (error: unknown) { console.debug("Failed to store log entry in vector database, using mock", { error: String(error), }); console.debug("Vector store: Storing log entry (mock)", entry.message, entry.level); } } /** * Index a diff entry in the vector database * @param entry Diff entry to index */ export async function indexDiffEntry(entry: DiffEntry): Promise<void> { try { // If OpenAI client is not available, use mock implementation if (!openai) { console.debug("Vector store: Indexing diff entry (mock)", entry.id, entry.file); return; } // Ensure vector store is initialized const storeId = await initializeVectorStore(); // Create a text file with the diff entry const content = `Diff Entry (${entry.id}) File: ${entry.file} Diff: ${entry.diff} Metadata: ${JSON.stringify(entry.metadata || {}, null, 2)}`; const file = new File( [content], `diff-${entry.id}.txt`, { type: "text/plain" }, ); // Upload the file to OpenAI const uploadedFile = await openai.files.create({ file, purpose: "assistants", }); // Add the file to the vector store await openai.vectorStores.files.create(storeId, { file_id: uploadedFile.id, }); console.debug("Diff entry indexed in vector database", { id: entry.id, file: entry.file, diffPreview: entry.diff.substring(0, 50) + (entry.diff.length > 50 ? "..." : ""), }); } catch (error: unknown) { console.debug("Failed to index diff entry in vector database, using mock", { error: String(error), }); console.debug("Vector store: Indexing diff entry (mock)", entry.id, entry.file); } } /** * Search for diff entries similar to the query * @param query Search query * @param maxResults Maximum number of results to return * @returns Array of matching diff entries with similarity scores */ export async function searchDiffEntries( query: string, maxResults: number = 5, ): Promise<VectorSearchResult[]> { try { // If OpenAI client is not available, use mock implementation if (!openai) { console.debug("Vector store: Searching for diff entries (mock)", query); return []; } // Ensure vector store is initialized const storeId = await initializeVectorStore(); // Search the vector store const searchResponse = await openai.vectorStores.search(storeId, { query, max_num_results: maxResults, }); // Transform the results into the expected format const results: VectorSearchResult[] = searchResponse.data.map((result: any) => { // Extract diff entry information from the content const content = result.content[0]?.text || ""; // Parse the content to extract diff entry information const idMatch = content.match(/Diff Entry \((.*?)\)/); const fileMatch = content.match(/File: (.*?)$/m); const diffMatch = content.match(/Diff:\n([\s\S]*?)Metadata:/); const metadataMatch = content.match(/Metadata: ([\s\S]*?)$/); const id = idMatch ? idMatch[1] : ""; const file = fileMatch ? fileMatch[1] : ""; const diff = diffMatch ? diffMatch[1].trim() : ""; const metadata = metadataMatch ? JSON.parse(metadataMatch[1]) : {}; return { entry: { id, file, diff, metadata, } as DiffEntry, score: result.score || 0, }; }); console.debug("Searched for diff entries", { query, maxResults, resultsCount: results.length, }); return results; } catch (error: unknown) { console.debug("Failed to search diff entries, using mock", { error: String(error) }); console.debug("Vector store: Searching for diff entries (mock)", query); return []; } } /** * Search for log entries similar to the query * @param query Search query * @param maxResults Maximum number of results to return * @returns Array of matching log entries with similarity scores */ export async function searchLogEntries( query: string, maxResults: number = 5, ): Promise<VectorSearchResult[]> { try { // If OpenAI client is not available, use mock implementation if (!openai) { console.debug("Vector store: Searching for log entries (mock)", query); return []; } // Ensure vector store is initialized const storeId = await initializeVectorStore(); // Search the vector store const searchResponse = await openai.vectorStores.search(storeId, { query, max_num_results: maxResults, }); // Transform the results into the expected format const results: VectorSearchResult[] = searchResponse.data.map((result: any) => { // Extract log entry information from the content const content = result.content[0]?.text || ""; // Parse the content to extract log entry information const timestampMatch = content.match(/Log Entry \((.*?)\)/); const levelMatch = content.match(/Level: (.*?)$/m); const messageMatch = content.match(/Message: (.*?)$/m); const metadataMatch = content.match(/Metadata: ([\s\S]*?)$/); const timestamp = timestampMatch ? timestampMatch[1] : new Date().toISOString(); const level = levelMatch ? levelMatch[1] : "info"; const message = messageMatch ? messageMatch[1] : ""; const metadata = metadataMatch ? JSON.parse(metadataMatch[1]) : {}; return { entry: { timestamp, level, message, metadata, } as LogEntry, score: result.score || 0, }; }); console.debug("Searched for log entries", { query, maxResults, resultsCount: results.length, }); return results; } catch (error: unknown) { console.debug("Failed to search log entries, using mock", { error: String(error) }); console.debug("Vector store: Searching for log entries (mock)", query); return []; } } /** * Clear all entries from the vector store * Useful for testing or resetting the database */ export async function clearVectorStore(): Promise<void> { try { // If OpenAI client is not available or no vector store ID, use mock implementation if (!openai || !vectorStoreId) { console.debug("Vector store: Clearing all entries (mock)"); vectorStoreId = null; return; } // Get all files in the vector store const files = await openai.vectorStores.files.list(vectorStoreId); // Delete each file for (const file of files.data) { await openai.vectorStores.files.delete(vectorStoreId, file.id); } // Reset the vector store ID vectorStoreId = null; console.debug("Cleared all entries from vector store"); } catch (error: unknown) { console.debug("Failed to clear vector store, using mock", { error: String(error) }); console.debug("Vector store: Clearing all entries (mock)"); vectorStoreId = null; } } export async function searchVectorStore( query: string, maxResults: number = 10, ): Promise<VectorSearchResult[]> { // Split the max results between logs and diffs const halfResults = Math.ceil(maxResults / 2); // Search both logs and diffs const [logResults, diffResults] = await Promise.all([ searchLogEntries(query, halfResults), searchDiffEntries(query, halfResults), ]); // Combine and sort results by score const combinedResults = [...logResults, ...diffResults] .sort((a, b) => b.score - a.score) .slice(0, maxResults); return combinedResults; }