diff --git a/apps/server/package.json b/apps/server/package.json index 14ce7e5a..48ce3604 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -19,6 +19,7 @@ }, "description": "", "devDependencies": { + "@types/ms": "^2.1.0", "@types/node": "^24.2.0", "@types/nodemailer": "^6.4.17", "@types/pg": "^8.15.5", @@ -28,22 +29,20 @@ "tsx": "^4.20.3" }, "dependencies": { - "@ai-sdk/google": "^1.2.22", - "@ai-sdk/openai": "^1.3.22", + "@ai-sdk/google": "^2.0.6", + "@ai-sdk/openai": "^2.0.15", "@aws-sdk/client-s3": "^3.863.0", "@colanode/core": "*", "@colanode/crdt": "*", "@fastify/cors": "^11.0.1", "@fastify/websocket": "^11.1.0", - "@mastra/core": "latest", - "@mastra/memory": "latest", - "@mastra/pg": "^0.13.1", - "@mastra/rag": "^1.0.6", "@node-rs/argon2": "^2.0.2", - "ai": "^4.3.19", + "@opentelemetry/auto-instrumentations-node": "^0.62.1", + "@opentelemetry/sdk-node": "^0.203.0", "@redis/client": "^5.8.0", "@tus/s3-store": "^2.0.0", "@tus/server": "^2.3.0", + "ai": "^5.0.15", "bullmq": "^5.56.9", "diff": "^8.0.2", "dotenv": "^17.2.1", @@ -53,8 +52,7 @@ "js-sha256": "^0.11.0", "ky": "^1.8.2", "kysely": "^0.28.4", - "langchain": "^0.3.30", - "langfuse-langchain": "^3.38.4", + "langfuse-vercel": "^3.38.4", "ms": "^2.1.3", "nodemailer": "^7.0.5", "pg": "^8.16.3", diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index a830e91c..ded744b2 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -6,6 +6,8 @@ import { initRedis } from '@colanode/server/data/redis'; import { eventBus } from '@colanode/server/lib/event-bus'; import { emailService } from '@colanode/server/services/email-service'; import { jobService } from '@colanode/server/services/job-service'; +import { initObservability } from '@colanode/server/lib/observability/otel'; +import { initAssistantTrigger } from '@colanode/server/services/assistant-trigger'; dotenv.config({ quiet: true, @@ -22,6 +24,11 @@ const init = async () => { await eventBus.init(); await emailService.init(); + + // Subscribe after event bus init and job queue ready + initAssistantTrigger(); + + initObservability(); }; init(); diff --git a/apps/server/src/jobs/assistant-response.ts b/apps/server/src/jobs/assistant-response.ts index f9c5275a..2cb1d6ba 100644 --- a/apps/server/src/jobs/assistant-response.ts +++ b/apps/server/src/jobs/assistant-response.ts @@ -5,12 +5,10 @@ import { getNodeModel, MessageAttributes, } from '@colanode/core'; -import { Mastra } from '@mastra/core'; -import { RuntimeContext } from '@mastra/core/runtime-context'; import { database } from '@colanode/server/data/database'; import { SelectNode } from '@colanode/server/data/schema'; import { JobHandler } from '@colanode/server/jobs'; -import { assistantWorkflow } from '@colanode/server/lib/ai/ai-workflow'; +import { runAssistantWorkflow } from '@colanode/server/lib/ai/ai-workflow'; import { AssistantWorkflowInput, AssistantWorkflowOutput, @@ -91,10 +89,10 @@ export const assistantRespondHandler: JobHandler< console.log(`šŸš€ Processing AI assistant request for message: ${messageId}`); const startTime = Date.now(); - // Prepare request for the AI service const assistantRequest: AssistantWorkflowInput = { userInput: messageText, workspaceId, + workspaceName: workspace.name || workspaceId, userId: user.id, userDetails: { name: user.name || 'User', @@ -105,54 +103,19 @@ export const assistantRespondHandler: JobHandler< selectedContextNodeIds, }; - // Prepare runtime context - const runtimeContext = new RuntimeContext(); - runtimeContext.set('workspaceName', workspace.name || workspaceId); - runtimeContext.set('userName', user.name || 'User'); - runtimeContext.set('userEmail', user.email || ''); - runtimeContext.set('workspaceId', workspaceId); - runtimeContext.set('userId', user.id); - runtimeContext.set('selectedContextNodeIds', selectedContextNodeIds || []); - runtimeContext.set('userInput', messageText); - - // Initialize Mastra and get the workflow - const mastra = new Mastra({ - workflows: { - assistantWorkflow, - }, - }); - const workflow = mastra.getWorkflow('assistantWorkflow'); - const run = await workflow.createRunAsync(); - - // Execute the workflow - const result = await run.start({ - inputData: assistantRequest, - runtimeContext, - }); - - if (result.status !== 'success' || !result.result) { - const errorMessage = - result.status === 'suspended' - ? 'Workflow was suspended unexpectedly' - : (result as any).error || 'Workflow execution failed'; - console.error('āŒ Workflow failed:', errorMessage); - throw new Error(errorMessage); - } - - const assistantResult: AssistantWorkflowOutput = { - ...result.result, - processingTimeMs: Date.now() - startTime, - }; + const result: AssistantWorkflowOutput = + await runAssistantWorkflow(assistantRequest); + result.processingTimeMs = Date.now() - startTime; console.log( - `āœ… AI response generated (${assistantResult.processingTimeMs}ms): ${ - assistantResult.searchPerformed ? 'with search' : 'no search' + `āœ… AI response generated (${result.processingTimeMs}ms): ${ + result.searchPerformed ? 'with search' : 'no search' }` ); await createAndPublishResponse( - assistantResult.finalAnswer, - assistantResult.citations, + result.finalAnswer, + result.citations, message, workspaceId ); diff --git a/apps/server/src/jobs/document-embed-scan.ts b/apps/server/src/jobs/document-embed-scan.ts index 3c76811d..7c078587 100644 --- a/apps/server/src/jobs/document-embed-scan.ts +++ b/apps/server/src/jobs/document-embed-scan.ts @@ -25,6 +25,7 @@ declare module '@colanode/server/jobs' { export const documentEmbedScanHandler: JobHandler< DocumentEmbedScanInput > = async () => { + console.log('document embed scan job'); if (!config.ai.enabled) { return; } @@ -55,7 +56,7 @@ export const documentEmbedScanHandler: JobHandler< .where('document_id', '=', document.id) .execute(); - return; + continue; } const firstEmbedding = await database diff --git a/apps/server/src/jobs/document-embed.ts b/apps/server/src/jobs/document-embed.ts index f89b0af4..0477d9a3 100644 --- a/apps/server/src/jobs/document-embed.ts +++ b/apps/server/src/jobs/document-embed.ts @@ -1,4 +1,3 @@ -import { createOpenAI } from '@ai-sdk/openai'; import { embedMany } from 'ai'; import { sql } from 'kysely'; @@ -7,6 +6,7 @@ import { database } from '@colanode/server/data/database'; import { CreateDocumentEmbedding } from '@colanode/server/data/schema'; import { JobHandler } from '@colanode/server/jobs'; import { chunkText } from '@colanode/server/lib/ai/chunking'; +import { getEmbeddingModel } from '@colanode/server/lib/ai/ai-models'; import { config } from '@colanode/server/lib/config'; import { fetchNode } from '@colanode/server/lib/nodes'; @@ -26,10 +26,13 @@ declare module '@colanode/server/jobs' { export const documentEmbedHandler: JobHandler = async ( input ) => { + try { if (!config.ai.enabled) { return; } + console.log('document embed job', input); + const { documentId } = input; const document = await database .selectFrom('documents') @@ -37,17 +40,21 @@ export const documentEmbedHandler: JobHandler = async ( .where('id', '=', documentId) .executeTakeFirst(); + console.log('document', document?.id); if (!document) { + console.log('document not found'); return; } const node = await fetchNode(documentId); if (!node) { + console.log('node not found'); return; } const nodeModel = getNodeModel(node.type); if (!nodeModel?.documentSchema) { + console.log('node model not found'); return; } @@ -58,28 +65,36 @@ export const documentEmbedHandler: JobHandler = async ( .where('document_id', '=', documentId) .execute(); + console.log('no text'); return; } - const openaiClient = createOpenAI({ apiKey: config.ai.embedding.apiKey }); - const embeddingModel = openaiClient.embedding(config.ai.embedding.modelName); + console.log('sending request to openai'); + console.log('config.ai.embedding.apiKey', config.ai.embedding.apiKey); + + const embeddingModel = getEmbeddingModel(); + + console.log('getting existing embeddings'); const existingEmbeddings = await database .selectFrom('document_embeddings') .select(['chunk', 'revision', 'text', 'summary']) .where('document_id', '=', documentId) .execute(); + console.log('existing embeddings', existingEmbeddings.length); + const revision = existingEmbeddings.length > 0 ? existingEmbeddings[0]!.revision : 0n; if (revision >= document.revision) { + console.log('revision is up to date'); return; } const textChunks = await chunkText( text, - existingEmbeddings.map((e) => ({ + existingEmbeddings.map((e: { text: string; summary: string | null }) => ({ text: e.text, summary: e.summary ?? undefined, })), @@ -87,14 +102,20 @@ export const documentEmbedHandler: JobHandler = async ( ); const embeddingsToUpsert: CreateDocumentEmbedding[] = []; + console.log('textChunks', textChunks.length); for (let i = 0; i < textChunks.length; i++) { + console.log('chunk', i); const chunk = textChunks[i]; if (!chunk) { + console.log('chunk is undefined'); continue; } - const existing = existingEmbeddings.find((e) => e.chunk === i); + const existing = existingEmbeddings.find( + (e: { chunk: number; text: string }) => e.chunk === i + ); if (existing && existing.text === chunk.text) { + console.log('chunk already exists'); continue; } @@ -110,6 +131,8 @@ export const documentEmbedHandler: JobHandler = async ( }); } + console.log('embeddingsToUpsert', embeddingsToUpsert.length); + const batchSize = config.ai.embedding.batchSize; for (let i = 0; i < embeddingsToUpsert.length; i += batchSize) { const batch = embeddingsToUpsert.slice(i, i + batchSize); @@ -117,10 +140,18 @@ export const documentEmbedHandler: JobHandler = async ( item.summary ? `${item.summary}\n\n${item.text}` : item.text ); + console.log('calling embedMany'); const { embeddings: embeddingVectors } = await embedMany({ model: embeddingModel, values: textsToEmbed, + providerOptions: { + openai: { + dimensions: config.ai.embedding.dimensions, + }, + }, }); + + console.log('embedding vectors', embeddingVectors.length); for (let j = 0; j < batch.length; j++) { const vector = embeddingVectors[j]; const batchItem = batch[j]; @@ -130,33 +161,43 @@ export const documentEmbedHandler: JobHandler = async ( } } - if (embeddingsToUpsert.length === 0) { + // Filter out entries with empty vectors + const ready = embeddingsToUpsert.filter((e) => e.embedding_vector.length > 0); + if (ready.length === 0) { + console.log('no embeddings to upsert'); return; } - await database - .insertInto('document_embeddings') - .values( - embeddingsToUpsert.map((embedding) => ({ - document_id: embedding.document_id, - chunk: embedding.chunk, - revision: embedding.revision, - workspace_id: embedding.workspace_id, - text: embedding.text, - summary: embedding.summary, - embedding_vector: sql.raw( - `'[${embedding.embedding_vector.join(',')}]'::vector` - ), - created_at: embedding.created_at, - })) - ) - .onConflict((oc) => - oc.columns(['document_id', 'chunk']).doUpdateSet({ - text: sql.ref('excluded.text'), - summary: sql.ref('excluded.summary'), - embedding_vector: sql.ref('excluded.embedding_vector'), - updated_at: new Date(), - }) - ) - .execute(); + console.log('upserting embeddings'); + await database + .insertInto('document_embeddings') + .values( + ready.map((embedding) => ({ + document_id: embedding.document_id, + chunk: embedding.chunk, + revision: embedding.revision, + workspace_id: embedding.workspace_id, + text: embedding.text, + summary: embedding.summary, + embedding_vector: sql.raw( + `'[${embedding.embedding_vector.join(',')}]'::vector` + ), + created_at: embedding.created_at, + })) + ) + .onConflict((oc: any) => + oc.columns(['document_id', 'chunk']).doUpdateSet({ + text: sql.ref('excluded.text'), + summary: sql.ref('excluded.summary'), + embedding_vector: sql.ref('excluded.embedding_vector'), + updated_at: new Date(), + }) + ) + .execute(); + } catch (error) { + console.log('error upserting embeddings', error); + throw error; + } finally { + console.log('clearing document embedding schedule'); + } }; diff --git a/apps/server/src/jobs/node-embed-scan.ts b/apps/server/src/jobs/node-embed-scan.ts index 5187c310..caa8df62 100644 --- a/apps/server/src/jobs/node-embed-scan.ts +++ b/apps/server/src/jobs/node-embed-scan.ts @@ -25,6 +25,7 @@ declare module '@colanode/server/jobs' { export const nodeEmbedScanHandler: JobHandler< NodeEmbedScanInput > = async () => { + console.log('node embed scan job'); if (!config.ai.enabled) { return; } diff --git a/apps/server/src/jobs/node-embed.ts b/apps/server/src/jobs/node-embed.ts index b1b9f720..c23831ac 100644 --- a/apps/server/src/jobs/node-embed.ts +++ b/apps/server/src/jobs/node-embed.ts @@ -1,4 +1,3 @@ -import { createOpenAI } from '@ai-sdk/openai'; import { embedMany } from 'ai'; import { sql } from 'kysely'; @@ -7,6 +6,7 @@ import { database } from '@colanode/server/data/database'; import { CreateNodeEmbedding } from '@colanode/server/data/schema'; import { JobHandler } from '@colanode/server/jobs'; import { chunkText } from '@colanode/server/lib/ai/chunking'; +import { getEmbeddingModel } from '@colanode/server/lib/ai/ai-models'; import { config } from '@colanode/server/lib/config'; import { fetchNode } from '@colanode/server/lib/nodes'; @@ -58,8 +58,7 @@ export const nodeEmbedHandler: JobHandler = async (input) => { return; } - const openaiClient = createOpenAI({ apiKey: config.ai.embedding.apiKey }); - const embeddingModel = openaiClient.embedding(config.ai.embedding.modelName); + const embeddingModel = getEmbeddingModel(); const existingEmbeddings = await database .selectFrom('node_embeddings') @@ -79,7 +78,7 @@ export const nodeEmbedHandler: JobHandler = async (input) => { const textChunks = await chunkText( fullText, - existingEmbeddings.map((e) => ({ + existingEmbeddings.map((e: { text: string; summary: string | null }) => ({ text: e.text, summary: e.summary ?? undefined, })), @@ -93,7 +92,9 @@ export const nodeEmbedHandler: JobHandler = async (input) => { continue; } - const existing = existingEmbeddings.find((e) => e.chunk === i); + const existing = existingEmbeddings.find( + (e: { chunk: number; text: string }) => e.chunk === i + ); if (existing && existing.text === chunk.text) { continue; } @@ -124,6 +125,11 @@ export const nodeEmbedHandler: JobHandler = async (input) => { const { embeddings: embeddingVectors } = await embedMany({ model: embeddingModel, values: textsToEmbed, + providerOptions: { + openai: { + dimensions: config.ai.embedding.dimensions, + }, + }, }); for (let j = 0; j < batch.length; j++) { const vector = embeddingVectors[j]; @@ -134,10 +140,16 @@ export const nodeEmbedHandler: JobHandler = async (input) => { } } + // Filter out entries with empty vectors + const ready = embeddingsToUpsert.filter((e) => e.embedding_vector.length > 0); + if (ready.length === 0) { + return; + } + await database .insertInto('node_embeddings') .values( - embeddingsToUpsert.map((embedding) => ({ + ready.map((embedding) => ({ node_id: embedding.node_id, chunk: embedding.chunk, revision: embedding.revision, diff --git a/apps/server/src/lib/ai/ai-agents.ts b/apps/server/src/lib/ai/ai-agents.ts deleted file mode 100644 index cc47e010..00000000 --- a/apps/server/src/lib/ai/ai-agents.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { Agent } from '@mastra/core/agent'; -import { ModelConfig } from './ai-models'; - -export const createIntentAgent = () => { - return new Agent({ - name: 'Intent Classifier', - description: - 'Classifies whether queries need workspace data or general knowledge', - instructions: ({ runtimeContext }) => { - const workspaceName = runtimeContext?.get('workspaceName') || 'workspace'; - const userName = runtimeContext?.get('userName') || 'user'; - - return `You classify user queries. Your only job is to determine if the query needs workspace data. - -WORKSPACE: ${workspaceName} -USER: ${userName} - -CLASSIFICATION RULES: -- "no_context" = General knowledge, explanations, how-to questions -- "retrieve" = Workspace content, documents, people, or data - -EXAMPLES: -"What is JavaScript?" → no_context -"How do I code?" → no_context -"Explain databases" → no_context -"Find my documents" → retrieve -"Show recent files" → retrieve -"What did John write?" → retrieve - -Respond with just the classification and confidence (0-1).`; - }, - model: () => ModelConfig.forIntentRecognition(), - }); -}; - -export const createQueryAgent = () => { - return new Agent({ - name: 'Query Optimizer', - description: 'Rewrites queries for better search performance', - instructions: () => { - return `You optimize search queries. Your only job is to rewrite queries for better search results. - -TASK: Take a user query and create two optimized versions: -1. SEMANTIC QUERY: Natural language for vector search -2. KEYWORD QUERY: Key terms for text search - -RULES: -- Remove filler words (the, a, an, is, are, etc.) -- Focus on core concepts and entities -- Keep important context words -- Make queries concise but complete - -EXAMPLES: -Input: "Show me documents about the marketing campaign John worked on last month" -Semantic: "marketing campaign documents John authored recent" -Keyword: "marketing campaign John documents month" - -Input: "Find all completed projects from Q3" -Semantic: "completed projects third quarter finished" -Keyword: "completed projects Q3 done finished" - -Respond with just the two optimized queries.`; - }, - model: () => ModelConfig.forIntentRecognition(), - }); -}; - -export const createAnswerAgent = () => { - return new Agent({ - name: 'Answer Generator', - description: - 'Generates helpful responses using context or general knowledge', - instructions: ({ runtimeContext }) => { - const workspaceName = runtimeContext?.get('workspaceName') || 'workspace'; - const userName = runtimeContext?.get('userName') || 'user'; - - return `You are a helpful assistant for the ${workspaceName} workspace. - -USER: ${userName} - -YOUR JOB: -- Answer questions clearly and helpfully -- Use provided context when available -- Cite sources when using context: "According to [Source 1]..." -- If no context, use general knowledge -- Be professional but conversational - -CONTEXT RULES: -- WITH context: Use it as primary source, cite sources -- WITHOUT context: Use general knowledge, don't mention workspace - -RESPONSE STYLE: -- Clear and direct -- Professional but friendly -- Actionable when possible -- Acknowledge limitations honestly - -Keep responses focused and helpful.`; - }, - model: () => ModelConfig.forAssistant(), - }); -}; - -export const createRerankAgent = () => { - return new Agent({ - name: 'Relevance Scorer', - description: 'Scores search results for relevance to user query', - instructions: () => { - return `You score search results for relevance. Your only job is to rate how well each result matches the user's query. - -TASK: Score each result from 0.0 to 1.0 based on relevance. - -SCORING GUIDE: -- 1.0 = Perfect match, exactly what user needs -- 0.8 = Very relevant, mostly matches query -- 0.6 = Somewhat relevant, partially matches -- 0.4 = Minimally relevant, tangentially related -- 0.2 = Low relevance, barely related -- 0.0 = Not relevant, unrelated to query - -CONSIDER: -- Topic match (does content match the query topic?) -- Specificity (does it answer the specific question?) -- Completeness (does it provide useful information?) -- Recency (newer content often more relevant) - -Return just the scores for each item.`; - }, - model: () => ModelConfig.forReranking(), - }); -}; - -export const createChunkEnrichmentAgent = () => { - return new Agent({ - name: 'Chunk Enrichment', - description: - 'Creates contextual summaries for text chunks to enhance search retrieval', - instructions: () => { - return `You create concise summaries of text chunks to improve search retrieval. Your only job is to summarize the given chunk within its document context. - -TASK: Generate a brief summary (30-50 words) of the chunk that captures its key points and role in the larger document. - -GUIDELINES: -- Focus on the main ideas and key information in the chunk -- Consider how the chunk fits into the complete document -- Identify the chunk's purpose or role (introduction, data, conclusion, etc.) -- Use descriptive, neutral language -- Make the summary useful for search and retrieval -- Different content types need different approaches: - * "message": Focus on communication content and context - * "page": Identify document structure and main topics - * "record": Describe the type of data and key fields - * Other types: Adapt summary to content purpose - -OUTPUT: Provide only the summary with no additional text or explanations.`; - }, - model: () => ModelConfig.forIntentRecognition(), - }); -}; - -export const AgentConfig = { - INTENT_AGENT: 'intent-classifier', - QUERY_AGENT: 'query-optimizer', - ANSWER_AGENT: 'answer-generator', - RERANK_AGENT: 'relevance-scorer', - CHUNK_ENRICHMENT_AGENT: 'chunk-enrichment', -} as const; diff --git a/apps/server/src/lib/ai/ai-demo.ts b/apps/server/src/lib/ai/ai-demo.ts deleted file mode 100644 index 87d2dc6b..00000000 --- a/apps/server/src/lib/ai/ai-demo.ts +++ /dev/null @@ -1,460 +0,0 @@ -/** - * AI Assistant Demo and Examples - * - * This file demonstrates how to use the new AI workflow-based assistant system - * and provides examples for different use cases. - */ - -import { Mastra } from '@mastra/core'; -import { RuntimeContext } from '@mastra/core/runtime-context'; -import { assistantWorkflow } from './ai-workflow'; -import { - AssistantWorkflowInput, - AssistantWorkflowOutput, -} from '@colanode/server/types/ai'; - -/** - * Process an AI request using the Mastra workflow directly. - * - * @param request - The user's request with context - * @returns Promise resolving to the assistant's response - */ -async function processAIRequest( - request: AssistantWorkflowInput -): Promise { - const startTime = Date.now(); - - // Prepare runtime context - const runtimeContext = new RuntimeContext(); - runtimeContext.set('workspaceName', request.workspaceId); - runtimeContext.set('userName', request.userDetails.name); - runtimeContext.set('userEmail', request.userDetails.email); - runtimeContext.set('workspaceId', request.workspaceId); - runtimeContext.set('userId', request.userId); - runtimeContext.set( - 'selectedContextNodeIds', - request.selectedContextNodeIds || [] - ); - runtimeContext.set('userInput', request.userInput); - - // Initialize Mastra and get the workflow - const mastra = new Mastra({ - workflows: { - assistantWorkflow, - }, - }); - const workflow = mastra.getWorkflow('assistantWorkflow'); - const run = await workflow.createRunAsync(); - - // Execute the workflow - const result = await run.start({ - inputData: request, - runtimeContext, - }); - - if (result.status !== 'success' || !result.result) { - const errorMessage = - result.status === 'suspended' - ? 'Workflow was suspended unexpectedly' - : (result as any).error || 'Workflow execution failed'; - console.error('āŒ Workflow failed:', errorMessage); - throw new Error(errorMessage); - } - - return { - ...result.result, - processingTimeMs: Date.now() - startTime, - }; -} - -/** - * Demo: Basic AI Assistant Usage - * - * Shows how to use the AI service for simple requests - */ -export async function demoBasicUsage() { - console.log('\nšŸ¤– === Basic AI Assistant Usage Demo ==='); - - try { - const response = await processAIRequest({ - userInput: 'What are the latest documents about project management?', - workspaceId: 'demo_workspace_123', - userId: 'demo_user_456', - userDetails: { - name: 'John Doe', - email: 'john@example.com', - }, - }); - - console.log( - 'šŸ“ User Query:', - 'What are the latest documents about project management?' - ); - console.log('šŸ¤– AI Response:', response.finalAnswer); - console.log('šŸ” Search Performed:', response.searchPerformed); - console.log('ā±ļø Processing Time:', `${response.processingTimeMs}ms`); - console.log('šŸ“š Citations:', response.citations.length); - } catch (error) { - console.error('āŒ Demo failed:', error); - } -} - -/** - * Demo: Complex Database Query Processing - * - * Shows how the service handles complex database queries - */ -export async function demoComplexDatabaseQuery() { - console.log('\nšŸ—„ļø === Complex Database Query Demo ==='); - - try { - const response = await processAIRequest({ - userInput: - 'Can you help me understand the Q3 sales data from our database?', - workspaceId: 'demo_workspace_123', - userId: 'demo_user_456', - userDetails: { - name: 'Jane Smith', - email: 'jane@example.com', - }, - }); - - console.log( - 'šŸ“ User Query:', - 'Can you help me understand the Q3 sales data from our database?' - ); - console.log('šŸ¤– AI Response:', response.finalAnswer); - console.log('šŸ” Search Performed:', response.searchPerformed); - console.log('ā±ļø Processing Time:', `${response.processingTimeMs}ms`); - console.log('šŸ“š Citations Found:', response.citations.length); - } catch (error) { - console.error('āŒ Complex query demo failed:', error); - } -} - -/** - * Demo: Workflow System Usage - * - * Shows how the new workflow system handles different types of queries - */ -export async function demoWorkflowUsage() { - console.log('\nšŸ”„ === Workflow System Demo ==='); - - try { - // Test general knowledge query (no_context intent) - console.log('\nšŸ“š Testing general knowledge query...'); - const generalResponse = await processAIRequest({ - userInput: 'What is TypeScript and why is it useful?', - workspaceId: 'demo_workspace_123', - userId: 'demo_user_456', - userDetails: { - name: 'John Doe', - email: 'john@example.com', - }, - }); - - console.log('šŸ“ Query: What is TypeScript and why is it useful?'); - console.log( - 'šŸ¤– Response:', - generalResponse.finalAnswer.substring(0, 200) + '...' - ); - console.log('šŸ” Search Performed:', generalResponse.searchPerformed); - console.log('šŸ“š Citations:', generalResponse.citations.length); - - // Test workspace-specific query (retrieve intent) - console.log('\nšŸ” Testing workspace-specific query...'); - const workspaceResponse = await processAIRequest({ - userInput: 'Show me recent documents about project planning', - workspaceId: 'demo_workspace_123', - userId: 'demo_user_456', - userDetails: { - name: 'Jane Smith', - email: 'jane@example.com', - }, - }); - - console.log('šŸ“ Query: Show me recent documents about project planning'); - console.log( - 'šŸ¤– Response:', - workspaceResponse.finalAnswer.substring(0, 200) + '...' - ); - console.log('šŸ” Search Performed:', workspaceResponse.searchPerformed); - console.log('šŸ“š Citations:', workspaceResponse.citations.length); - } catch (error) { - console.error('āŒ Workflow demo failed:', error); - } -} - -/** - * Demo: Error Handling - * - * Shows how the system handles various error scenarios - */ -export async function demoErrorHandling() { - console.log('\nāš ļø === Error Handling Demo ==='); - - // Test with invalid workspace ID - try { - console.log('Testing with invalid workspace ID...'); - const response = await processAIRequest({ - userInput: 'Test query', - workspaceId: 'invalid_workspace', - userId: 'test_user', - userDetails: { - name: 'Test User', - email: 'test@example.com', - }, - }); - - console.log('šŸ¤– Response (graceful error):', response.finalAnswer); - console.log('ā±ļø Processing Time:', `${response.processingTimeMs}ms`); - } catch (error) { - console.log( - 'āŒ Handled error gracefully:', - error instanceof Error ? error.message : 'Unknown error' - ); - } - - // Test with empty input - try { - console.log('Testing with empty input...'); - const response = await processAIRequest({ - userInput: '', - workspaceId: 'demo_workspace_123', - userId: 'test_user', - userDetails: { - name: 'Test User', - email: 'test@example.com', - }, - }); - - console.log('šŸ¤– Response (empty input):', response.finalAnswer); - } catch (error) { - console.log( - 'āŒ Handled empty input gracefully:', - error instanceof Error ? error.message : 'Unknown error' - ); - } -} - -/** - * Demo: Performance Testing - * - * Shows performance characteristics with different query types - */ -export async function demoPerformance() { - console.log('\n⚔ === Performance Testing Demo ==='); - - const queries = [ - { type: 'General Knowledge', query: 'What is TypeScript?' }, - { type: 'Simple Workspace', query: 'Show me recent documents' }, - { - type: 'Complex Database', - query: 'Find all projects where status is completed and priority is high', - }, - ]; - - for (const { type, query } of queries) { - try { - console.log(`\nšŸ”¬ Testing ${type} query...`); - console.log(`šŸ“ Query: "${query}"`); - - const startTime = Date.now(); - const response = await processAIRequest({ - userInput: query, - workspaceId: 'demo_workspace_123', - userId: 'perf_test_user', - userDetails: { - name: 'Performance Tester', - email: 'perf@example.com', - }, - }); - const totalTime = Date.now() - startTime; - - console.log('šŸ” Search Performed:', response.searchPerformed); - console.log('ā±ļø Service Time:', `${response.processingTimeMs}ms`); - console.log('ā±ļø Total Time:', `${totalTime}ms`); - console.log( - 'šŸ“ Response Length:', - `${response.finalAnswer.length} chars` - ); - } catch (error) { - console.error(`āŒ Performance test failed for ${type}:`, error); - } - } -} - -/** - * Demo: New Workflow Architecture - * - * Shows the new declarative workflow with proper branching - */ -export async function demoNewWorkflowArchitecture() { - console.log('\nšŸ—ļø === New Workflow Architecture Demo ==='); - - try { - // Test 1: No-context branch (general knowledge) - console.log('\n1ļøāƒ£ Testing NO_CONTEXT branch (general knowledge)...'); - const generalResponse = await processAIRequest({ - userInput: 'What is TypeScript and why should I use it?', - workspaceId: 'demo_workspace_123', - userId: 'demo_user_456', - userDetails: { - name: 'Alice Developer', - email: 'alice@example.com', - }, - }); - - console.log('šŸ“ Query: "What is TypeScript and why should I use it?"'); - console.log('šŸŽÆ Expected Branch: no_context'); - console.log( - 'šŸ” Search Performed:', - generalResponse.searchPerformed ? 'āŒ Unexpected' : 'āœ… None (correct)' - ); - console.log( - 'šŸ“š Citations:', - generalResponse.citations.length === 0 - ? 'āœ… None (correct)' - : 'āŒ Unexpected' - ); - console.log( - 'šŸ’¬ Response Preview:', - generalResponse.finalAnswer.substring(0, 150) + '...' - ); - - // Test 2: Retrieve branch (workspace-specific) - console.log('\n2ļøāƒ£ Testing RETRIEVE branch (workspace-specific)...'); - const workspaceResponse = await processAIRequest({ - userInput: 'Show me recent documents about project planning', - workspaceId: 'demo_workspace_123', - userId: 'demo_user_456', - userDetails: { - name: 'Bob Manager', - email: 'bob@example.com', - }, - }); - - console.log('šŸ“ Query: "Show me recent documents about project planning"'); - console.log('šŸŽÆ Expected Branch: retrieve'); - console.log( - 'šŸ” Search Performed:', - workspaceResponse.searchPerformed - ? 'āœ… Yes (correct)' - : 'āŒ None (unexpected)' - ); - console.log( - 'šŸ“š Citations:', - workspaceResponse.citations.length > 0 - ? 'āœ… Present (good)' - : 'āš ļø None (no results)' - ); - console.log( - 'šŸ’¬ Response Preview:', - workspaceResponse.finalAnswer.substring(0, 150) + '...' - ); - - console.log('\nāœ… New workflow architecture demo completed successfully!'); - } catch (error) { - console.error('āŒ New workflow architecture demo failed:', error); - } -} - -/** - * Run all demos including new architecture demonstrations - * - * Executes all demo functions to show the complete system capabilities - */ -export async function runAllDemos() { - console.log('šŸŽ¬ === AI Assistant System Demo Suite ==='); - console.log('This demo showcases the new Mastra-based AI assistant system\n'); - - try { - // Show the migration comparison first - showMigrationComparison(); - - // Demo new architecture - await demoNewWorkflowArchitecture(); - - // Run original demos for compatibility - await demoBasicUsage(); - await demoWorkflowUsage(); - await demoErrorHandling(); - await demoPerformance(); - - console.log('\nšŸŽ‰ === All Demos Completed Successfully ==='); - console.log('The new Mastra-based AI system is ready for production! šŸš€'); - } catch (error) { - console.error('āŒ Demo suite failed:', error); - } -} - -/** - * Migration comparison showing before vs after - */ -export function showMigrationComparison() { - console.log(` -šŸ”„ === MASTRA MIGRATION COMPLETED === - -šŸ“Š BEFORE (Complex LangChain System): -ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” -│ āŒ 600+ lines across multiple complex files │ -│ āŒ Manual LangGraph workflow with 14+ imperative nodes │ -│ āŒ Complex state management between workflow steps │ -│ āŒ Monolithic agents with multi-purpose prompts │ -│ āŒ Tightly coupled search and reranking logic │ -│ āŒ Poor observability and debugging capabilities │ -│ āŒ Not optimized for smaller/self-hosted LLMs │ -│ āŒ Difficult to test, maintain, and extend │ -ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ - -šŸ“Š AFTER (Declarative Mastra Workflow System): -ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” -│ āœ… Fully declarative workflow with proper branching │ -│ āœ… Single-purpose agents optimized for smaller LLMs │ -│ āœ… Granular tools with focused responsibilities │ -│ āœ… Intelligent intent-based routing │ -│ āœ… Full observability through Mastra's workflow engine │ -│ āœ… Type-safe, maintainable, and easily extensible │ -ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ - -šŸŽÆ KEY ACHIEVEMENTS: -• TRUE Mastra idiomatic implementation with .branch() routing -• BYOM (Bring Your Own Model) optimization for self-hosted LLMs -• Granular tools: semantic search, keyword search, database tools -• Step-by-step observability for debugging and optimization - -šŸ—ļø ARCHITECTURE HIGHLIGHTS: -• Declarative workflow: intentClassification → branch(intent) → publish -• Simplified agents: intentClassifier, queryOptimizer, answerGenerator - -šŸ“ REFACTORED FILE STRUCTURE: -ā”œā”€ā”€ ai-workflow.ts → Declarative Mastra workflow with branching -ā”œā”€ā”€ ai-agents.ts → Single-purpose agents optimized for small LLMs -ā”œā”€ā”€ ai-tools.ts → Granular tools (semantic, keyword, database) -ā”œā”€ā”€ ai-models.ts → Multi-provider model configuration -└── ai-demo.ts → Workflow path demonstrations -`); -} - -// Run demos if this file is executed directly -if (require.main === module) { - async function main() { - try { - showMigrationComparison(); - - console.log( - '\nāš ļø Note: These demos require valid workspace and user IDs to run properly.' - ); - console.log( - 'To run actual demos, update the IDs in the demo functions and uncomment the line below.\n' - ); - - // Uncomment to run actual demonstrations (requires valid workspace/user IDs) - // await runAllDemos(); - } catch (error) { - console.error('Demo error:', error); - } - } - - main(); -} diff --git a/apps/server/src/lib/ai/ai-models.ts b/apps/server/src/lib/ai/ai-models.ts index 3687a5cc..96c7fb5d 100644 --- a/apps/server/src/lib/ai/ai-models.ts +++ b/apps/server/src/lib/ai/ai-models.ts @@ -9,7 +9,9 @@ export type AITask = | 'response' // Main assistant responses | 'intentRecognition' // Intent classification | 'rerank' // Document reranking - | 'databaseFilter'; // Database filtering + | 'databaseFilter' // Database filtering + | 'queryRewrite' // Query rewriting + | 'contextEnhancer'; // Context enhancement for chunks export const getModelForTask = (task: AITask) => { if (!config.ai.enabled) { @@ -75,9 +77,31 @@ export const getAvailableProviders = (): AIProvider[] => { ) as AIProvider[]; }; +export const getEmbeddingModel = () => { + if (!config.ai.enabled) { + throw new Error('AI is disabled in configuration.'); + } + + const { provider, modelName, apiKey } = config.ai.embedding; + + if (provider === 'openai') { + process.env.OPENAI_API_KEY = apiKey; + return openai.textEmbeddingModel(modelName); + } + + if (provider === 'google') { + process.env.GOOGLE_GENERATIVE_AI_API_KEY = apiKey; + return google.textEmbedding(modelName); + } + + throw new Error(`Unsupported embedding provider: ${provider}`); +}; + export const ModelConfig = { forAssistant: () => getModelForTask('response'), forIntentRecognition: () => getModelForTask('intentRecognition'), forReranking: () => getModelForTask('rerank'), forDatabaseFilter: () => getModelForTask('databaseFilter'), + forQueryRewrite: () => getModelForTask('queryRewrite'), + forContextEnhancer: () => getModelForTask('contextEnhancer'), } as const; diff --git a/apps/server/src/lib/ai/ai-tools.ts b/apps/server/src/lib/ai/ai-tools.ts deleted file mode 100644 index 44f00343..00000000 --- a/apps/server/src/lib/ai/ai-tools.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { createTool } from '@mastra/core/tools'; -import { z } from 'zod'; - -export const createHybridSearchTool = () => - createTool({ - id: 'hybrid-search', - description: - 'Perform hybrid (semantic + keyword) search on workspace documents and nodes', - inputSchema: z.object({ - semanticQuery: z.string().describe('The semantic search query'), - keywordQuery: z.string().describe('The keyword search query'), - workspaceId: z.string().describe('The workspace ID to search in'), - userId: z.string().describe('The user ID for access control'), - maxResults: z - .number() - .default(10) - .describe('Maximum number of results to return'), - selectedContextNodeIds: z - .array(z.string()) - .optional() - .describe('Specific node IDs to search within'), - }), - outputSchema: z.object({ - results: z.array( - z.object({ - content: z - .string() - .describe('The content of the found document/node'), - type: z - .string() - .describe('The type of content (document, node, etc.)'), - sourceId: z.string().describe('Unique identifier for the source'), - score: z.number().describe('Hybrid relevance score (0-1 scaled)'), - metadata: z - .record(z.any()) - .describe('Additional metadata about the source'), - }) - ), - totalFound: z.number().describe('Total number of results returned'), - searchType: z.literal('hybrid').describe('Hybrid search'), - }), - execute: async ({ context }) => { - const { retrieveNodes } = await import( - '@colanode/server/lib/ai/node-retrievals' - ); - const { retrieveDocuments } = await import( - '@colanode/server/lib/ai/document-retrievals' - ); - - const { - semanticQuery, - keywordQuery, - workspaceId, - userId, - maxResults = 10, - selectedContextNodeIds = [], - } = context; - - try { - console.log( - `šŸ”Ž Hybrid search for: semantic="${semanticQuery}" keyword="${keywordQuery}" (max: ${maxResults})` - ); - - // Perform hybrid retrieval per source type - const [nodeResults, documentResults] = await Promise.all([ - retrieveNodes( - { - semanticQuery, - keywordQuery, - originalQuery: semanticQuery || keywordQuery, - intent: 'retrieve', - }, - workspaceId, - userId, - maxResults, - selectedContextNodeIds - ), - retrieveDocuments( - { - semanticQuery, - keywordQuery, - originalQuery: semanticQuery || keywordQuery, - intent: 'retrieve', - }, - workspaceId, - userId, - maxResults, - selectedContextNodeIds - ), - ]); - - const allResults = [...nodeResults, ...documentResults].slice( - 0, - maxResults - ); - - // Convert to standardized format (loosen typing for MDocument) - const results = allResults.map((doc) => { - const md = doc as any; - return { - content: md.pageContent || md.text || '', - type: md.metadata?.type || 'document', - sourceId: md.metadata?.id || md.id || '', - score: md.metadata?.score || md.score || 0, - metadata: md.metadata || {}, - }; - }); - - console.log(`āœ… Hybrid search returned ${results.length} results`); - - return { - results, - totalFound: results.length, - searchType: 'hybrid' as const, - }; - } catch (error) { - console.error('āŒ Hybrid search error:', error); - throw error; - } - }, - }); - -export const createDatabaseSchemaInspectionTool = () => - createTool({ - id: 'database-schema-inspection', - description: 'Get database schemas and structure for a workspace', - inputSchema: z.object({ - workspaceId: z.string().describe('The workspace ID'), - userId: z.string().describe('The user ID for access control'), - }), - outputSchema: z.object({ - databases: z.array( - z.object({ - id: z.string(), - name: z.string(), - fields: z.record( - z.object({ - type: z.string(), - name: z.string(), - }) - ), - sampleRecords: z - .array(z.any()) - .describe('Sample records for context'), - }) - ), - totalDatabases: z.number(), - }), - execute: async ({ context }) => { - const { retrieveByFilters } = await import( - '@colanode/server/lib/records' - ); - const { database } = await import('@colanode/server/data/database'); - - const { workspaceId, userId } = context; - - try { - console.log( - `šŸ—„ļø Inspecting database schemas for workspace: ${workspaceId}` - ); - - // Get available databases for the user - const databases = await database - .selectFrom('nodes as n') - .innerJoin('collaborations as c', 'c.node_id', 'n.root_id') - .where('n.type', '=', 'database') - .where('n.workspace_id', '=', workspaceId) - .where('c.collaborator_id', '=', userId) - .where('c.deleted_at', 'is', null) - .selectAll() - .execute(); - - if (databases.length === 0) { - console.log('šŸ“­ No accessible databases found'); - return { - databases: [], - totalDatabases: 0, - }; - } - - console.log(`šŸ” Found ${databases.length} accessible databases`); - - // Get database schemas and sample data - const databaseSchemas = await Promise.all( - databases.map(async (db: any) => { - const sampleRecords = await retrieveByFilters( - db.id, - workspaceId, - userId, - { filters: [], sorts: [], page: 1, count: 3 } // Just 3 samples - ); - - const dbAttrs = db.attributes as any; - const fields = dbAttrs.fields || {}; - const formattedFields = Object.entries(fields).reduce( - (acc: any, [id, field]: [string, any]) => ({ - ...acc, - [id]: { - type: field.type, - name: field.name, - }, - }), - {} - ); - - return { - id: db.id, - name: dbAttrs.name || 'Untitled Database', - fields: formattedFields, - sampleRecords, - }; - }) - ); - - console.log( - `āœ… Retrieved schemas for ${databaseSchemas.length} databases` - ); - - return { - databases: databaseSchemas, - totalDatabases: databaseSchemas.length, - }; - } catch (error) { - console.error('āŒ Database schema inspection error:', error); - throw error; - } - }, - }); - -export const createDatabaseQueryTool = () => - createTool({ - id: 'database-query', - description: 'Execute structured queries against workspace databases', - inputSchema: z.object({ - databaseId: z.string().describe('The database ID to query'), - workspaceId: z.string().describe('The workspace ID'), - userId: z.string().describe('The user ID for access control'), - filters: z.array(z.any()).describe('Structured filter conditions'), - maxResults: z.number().default(10).describe('Maximum number of results'), - }), - outputSchema: z.object({ - results: z.array( - z.object({ - content: z - .string() - .describe('Formatted content of the database record'), - metadata: z.object({ - id: z.string(), - type: z.literal('record'), - databaseId: z.string(), - databaseName: z.string(), - createdAt: z.date(), - createdBy: z.string(), - }), - }) - ), - totalFound: z.number(), - databaseName: z.string(), - }), - execute: async ({ context }) => { - const { retrieveByFilters } = await import( - '@colanode/server/lib/records' - ); - const { fetchNode } = await import('@colanode/server/lib/nodes'); - - const { - databaseId, - workspaceId, - userId, - filters, - maxResults = 10, - } = context; - - try { - console.log( - `šŸ—„ļø Querying database ${databaseId} with ${filters.length} filters` - ); - - // Get the database node for metadata - const dbNode = await fetchNode(databaseId); - if (!dbNode || dbNode.type !== 'database') { - console.log('āŒ Database not found or not accessible'); - return { - results: [], - totalFound: 0, - databaseName: 'Unknown Database', - }; - } - - const databaseName = - (dbNode.attributes as any).name || 'Untitled Database'; - - // Execute the query - const records = await retrieveByFilters( - databaseId, - workspaceId, - userId, - { filters, sorts: [], page: 1, count: maxResults } - ); - - // Format results - const results = records.map((record: any) => { - const fields = Object.entries((record.attributes as any).fields || {}) - .map(([key, value]) => `${key}: ${value}`) - .join('\n'); - - const content = `Database Record from ${databaseName}:\n${fields}`; - - return { - content, - metadata: { - id: record.id, - type: 'record' as const, - databaseId, - databaseName, - createdAt: record.created_at, - createdBy: record.created_by, - }, - }; - }); - - console.log( - `āœ… Database query returned ${results.length} records from ${databaseName}` - ); - - return { - results, - totalFound: results.length, - databaseName, - }; - } catch (error) { - console.error('āŒ Database query error:', error); - throw error; - } - }, - }); - -export const createAITools = () => ({ - hybridSearch: createHybridSearchTool(), - - databaseSchemaInspection: createDatabaseSchemaInspectionTool(), - databaseQuery: createDatabaseQueryTool(), -}); diff --git a/apps/server/src/lib/ai/ai-workflow.ts b/apps/server/src/lib/ai/ai-workflow.ts index c443db9c..497a18f1 100644 --- a/apps/server/src/lib/ai/ai-workflow.ts +++ b/apps/server/src/lib/ai/ai-workflow.ts @@ -1,500 +1,319 @@ -import { createWorkflow, createStep } from '@mastra/core/workflows'; +import { generateObject, generateText } from 'ai'; import { z } from 'zod'; +import { ModelConfig } from './ai-models'; +import { retrieveNodes } from '@colanode/server/lib/ai/node-retrievals'; +import { retrieveDocuments } from '@colanode/server/lib/ai/document-retrievals'; import { - createIntentAgent, - createAnswerAgent, - createQueryAgent, - createRerankAgent, -} from './ai-agents'; -import { createAITools } from './ai-tools'; -import { - assistantWorkflowInputSchema, - assistantWorkflowOutputSchema, - intentClassificationOutputSchema, - queryRewriteOutputSchema, - searchResultsOutputSchema, - rankedResultsOutputSchema, - answerOutputSchema, + AssistantWorkflowInput, + AssistantWorkflowOutput, + HybridSearchArgs, } from '@colanode/server/types/ai'; -const intentClassificationStep = createStep({ - id: 'intent-classification-step', - description: - 'Classify user intent: general knowledge vs workspace-specific query', - inputSchema: assistantWorkflowInputSchema, - outputSchema: intentClassificationOutputSchema, - execute: async ({ inputData, runtimeContext }) => { - const intentAgent = createIntentAgent(); +export async function runAssistantWorkflow( + input: AssistantWorkflowInput +): Promise { + const intent = await classifyIntent(input.userInput); - const prompt = `You are an intent classifier. Your only job is to determine if this query needs workspace data. + if (intent.intent === 'retrieve') { + const rewrites = await rewriteQuery(input.userInput); -Query: "${inputData.userInput}" - -Classify as: -- "no_context": General knowledge, explanations, calculations, definitions -- "retrieve": Workspace-specific content, documents, people, or data - -Examples: -- "What is TypeScript?" → no_context -- "How do I write clean code?" → no_context -- "Show me recent documents" → retrieve -- "Find projects by John" → retrieve - -Respond with just the classification and your confidence (0-1).`; - - try { - const response = await intentAgent.generate( - [{ role: 'user', content: prompt }], - { - runtimeContext, - output: z.object({ - intent: z.enum(['no_context', 'retrieve']), - confidence: z.number().min(0).max(1), - reasoning: z.string().optional(), - }), - } - ); - - console.log( - `šŸŽÆ Intent: ${response.object.intent} (confidence: ${response.object.confidence})` - ); - - return { - intent: response.object.intent, - confidence: response.object.confidence, - reasoning: response.object.reasoning, - originalInput: inputData.userInput, - }; - } catch (error) { - throw error; - } - }, -}); - -const queryRewriteStep = createStep({ - id: 'query-rewrite-step', - description: 'Optimize user query for semantic and keyword search', - inputSchema: intentClassificationOutputSchema, - outputSchema: queryRewriteOutputSchema, - execute: async ({ inputData, runtimeContext }) => { - const queryAgent = createQueryAgent(); - - const prompt = `Original Query: "${inputData.originalInput}" - -Create two optimized versions for search.`; - - try { - const response = await queryAgent.generate( - [{ role: 'user', content: prompt }], - { - runtimeContext, - output: z.object({ - semanticQuery: z.string(), - keywordQuery: z.string(), - }), - } - ); - - console.log(`šŸ” Semantic query: "${response.object.semanticQuery}"`); - console.log(`šŸ”‘ Keyword query: "${response.object.keywordQuery}"`); - - return { - semanticQuery: response.object.semanticQuery, - keywordQuery: response.object.keywordQuery, - originalQuery: inputData.originalInput, - intent: inputData.intent, - }; - } catch (error) { - throw error; - } - }, -}); - -const runSearchesStep = createStep({ - id: 'run-searches-step', - description: 'Execute hybrid (semantic + keyword) search via single tool', - inputSchema: queryRewriteOutputSchema, - outputSchema: searchResultsOutputSchema, - execute: async ({ inputData, runtimeContext }) => { - const workspaceId = runtimeContext?.get('workspaceId') as string; - const userId = runtimeContext?.get('userId') as string; - const selectedContextNodeIds = - (runtimeContext?.get('selectedContextNodeIds') as string[]) || []; - - if (!workspaceId || !userId) { - console.error('āŒ Missing required runtime context for search'); - throw new Error('Missing required runtime context for search'); - } - - console.log(`šŸ” Running hybrid search...`); - console.log(` Semantic: "${inputData.semanticQuery}"`); - console.log(` Keyword: "${inputData.keywordQuery}"`); - - try { - const tools = createAITools(); - - const hybridResults = await tools.hybridSearch.execute({ - context: { - semanticQuery: inputData.semanticQuery, - keywordQuery: inputData.keywordQuery, - workspaceId, - userId, - maxResults: 20, - selectedContextNodeIds, - }, - runtimeContext, - }); - - console.log( - `šŸ“Š Hybrid search completed: ${hybridResults.results.length} results` - ); - - return { - results: hybridResults.results, - searchType: 'hybrid' as const, - totalFound: hybridResults.totalFound, - }; - } catch (error) { - console.error('āŒ Search execution failed:', error); - throw error; - } - }, -}); - -const combineResultsStep = createStep({ - id: 'combine-results-step', - description: 'Combine and score search results using RRF algorithm', - inputSchema: searchResultsOutputSchema, - outputSchema: searchResultsOutputSchema, // Same schema for now - execute: async ({ inputData }) => { - if (inputData.results.length === 0) { - console.log('šŸ“­ No results to combine'); - return inputData; // Pass through if no results - } - - console.log(`šŸ”„ Combining ${inputData.results.length} search results`); - - // Simple combination: remove duplicates and apply recency boost - const uniqueResults = new Map(); - - inputData.results.forEach((result, index) => { - const key = result.sourceId; - - if (!uniqueResults.has(key)) { - // Apply position-based scoring (earlier results get higher scores) - const positionScore = 1 / (index + 1); - const recencyBoost = result.metadata.createdAt - ? Math.max( - 0, - 1 - - (Date.now() - new Date(result.metadata.createdAt).getTime()) / - (1000 * 60 * 60 * 24 * 30) - ) // 30 days - : 0; - - const combinedScore = - result.score * 0.7 + positionScore * 0.2 + recencyBoost * 0.1; - - uniqueResults.set(key, { - ...result, - score: Math.min(1, combinedScore), // Cap at 1.0 - }); - } + const { results } = await hybridSearch({ + semanticQuery: rewrites.semanticQuery, + keywordQuery: rewrites.keywordQuery, + workspaceId: input.workspaceId, + userId: input.userId, + maxResults: 20, + selectedContextNodeIds: input.selectedContextNodeIds, }); - // Sort by combined score - const combinedResults = Array.from(uniqueResults.values()) - .sort((a, b) => b.score - a.score) - .slice(0, 20); // Top 20 results + const combined = combineResults(results); - console.log(`āœ… Combined to ${combinedResults.length} unique results`); - - return { - results: combinedResults, - searchType: inputData.searchType, - totalFound: combinedResults.length, - }; - }, -}); - -const rerankStep = createStep({ - id: 'rerank-step', - description: 'Rerank search results for relevance using LLM', - inputSchema: searchResultsOutputSchema, - outputSchema: rankedResultsOutputSchema, - execute: async ({ inputData, runtimeContext }) => { - if (inputData.results.length === 0) { - console.log('šŸ“­ No results to rerank'); + if (combined.length === 0) { return { - rankedResults: [], + finalAnswer: + "I couldn't find relevant workspace context for that. Try rephrasing or selecting different context.", citations: [], - searchPerformed: false, - }; - } - - const originalQuery = - (runtimeContext?.get('userInput') as string) || 'query'; - console.log( - `šŸŽÆ Reranking ${inputData.results.length} results for query: "${originalQuery}"` - ); - - try { - const rerankAgent = createRerankAgent(); - - // Format results for reranking - const resultsText = inputData.results - .map( - (result, index) => `[${index}] ${result.content.substring(0, 300)}` - ) - .join('\n\n'); - - const prompt = `Query: "${originalQuery}" - -Results to score (0.0 to 1.0 for relevance): - -${resultsText} - -Score each result based on how well it answers the query.`; - - const response = await rerankAgent.generate( - [{ role: 'user', content: prompt }], - { - runtimeContext, - output: z.object({ - scores: z.array(z.number().min(0).max(1)), - }), - } - ); - - // Apply scores and sort by relevance - const rankedResults = inputData.results - .map((result, index) => ({ - content: result.content, - sourceId: result.sourceId, - relevanceScore: response.object.scores[index] || 0.5, - type: result.type, - metadata: result.metadata, - })) - .sort((a, b) => b.relevanceScore - a.relevanceScore) - .slice(0, 20); // Top 20 results - - // Generate citations from top results - const citations = rankedResults - .slice(0, 10) // Top 10 for citations - .map((result) => ({ - sourceId: result.sourceId, - quote: - result.content.substring(0, 200).trim() + - (result.content.length > 200 ? '...' : ''), - })); - - console.log( - `āœ… Reranked to ${rankedResults.length} results with ${citations.length} citations` - ); - - return { - rankedResults, - citations, searchPerformed: true, }; - } catch (error) { - throw error; - } - }, -}); - -const answerWithContextStep = createStep({ - id: 'answer-with-context-step', - description: 'Generate response using retrieved context and citations', - inputSchema: rankedResultsOutputSchema, - outputSchema: answerOutputSchema, - execute: async ({ inputData, runtimeContext }) => { - const answerAgent = createAnswerAgent(); - const userQuery = - (runtimeContext?.get('userInput') as string) || 'User query'; - - if (inputData.rankedResults.length === 0) { - throw new Error('No context available for answering the query'); } - // Format context for the LLM - const contextText = inputData.rankedResults - .slice(0, 10) // Top 10 results - .map((item, index) => `[Source ${index + 1}] ${item.content}`) - .join('\n\n'); + const ranked = await rerankLLM(input.userInput, combined); - console.log( - `šŸ“š Generating response with ${inputData.rankedResults.length} context items` + const { answer, citations } = await answerWithContext( + input.userInput, + ranked, + { + workspaceName: input.workspaceName, + userName: input.userDetails.name, + } ); - const prompt = `Answer the user's question using the provided workspace context. + return { finalAnswer: answer, citations, searchPerformed: true }; + } -**User Question:** ${userQuery} + const answer = await answerDirect(input.userInput, { + workspaceName: input.workspaceName, + userName: input.userDetails.name, + }); -**Workspace Context:** -${contextText} + return { finalAnswer: answer, citations: [], searchPerformed: false }; +} -**Instructions:** -- Use the context as your primary information source -- Cite sources using [Source N] format when referencing context -- If the context doesn't fully answer the question, combine it with general knowledge but clearly distinguish between the two -- Provide specific, actionable information when possible -- Be conversational but professional`; +async function classifyIntent(query: string) { + const schema = z.object({ + intent: z.enum(['no_context', 'retrieve']), + confidence: z.number().min(0).max(1), + reasoning: z.string().optional(), + }); - try { - const response = await answerAgent.generate( - [{ role: 'user', content: prompt }], - { runtimeContext } - ); + const { object } = await generateObject({ + model: ModelConfig.forIntentRecognition(), + schema, + prompt: ` +Decide if the user question requires searching workspace context. - console.log( - `āœ… Generated contextual response (${response.text.length} characters)` - ); +Return JSON { "intent": "retrieve"|"no_context", "confidence": 0..1, "reasoning": "" }. +Guidelines: +- "retrieve" if the answer likely depends on workspace content (notes, pages, records, files, chats) OR references specific people, dates, projects, or "this/that" items. +- "no_context" for general knowledge, chit-chat, or requests that don't reference workspace data. + +Question: "${query}" +`, + experimental_telemetry: { + isEnabled: true, + functionId: 'intent-classification', + mxetadata: { stage: 'intent', userQuery: query }, + }, + }); + + return object; +} + +async function rewriteQuery(original: string) { + const schema = z.object({ + semanticQuery: z.string(), + keywordQuery: z.string(), + }); + + const { object } = await generateObject({ + model: ModelConfig.forQueryRewrite(), + schema, + prompt: ` +Rewrite the user's input into two queries: + +1) "semanticQuery": A short natural-language search query (5–20 tokens), with expanded entities, acronyms, and likely synonyms. Drop punctuation and noise. +2) "keywordQuery": A Postgres websearch_to_tsquery string with: + - quoted phrases for exact matches + - important terms first + - optional synonyms after OR + - minus terms if the user excluded something + - keep it <= 15 tokens + +Example: +Input: "oncall runbook for payments db; not the legacy doc" +semanticQuery: "payments database oncall runbook escalation procedures current" +keywordQuery: "\"payments database\" runbook escalation -legacy" + +Input: "${original}" +Return JSON with both fields only. +`, + experimental_telemetry: { + isEnabled: true, + functionId: 'query-rewrite', + metadata: { stage: 'rewrite', original }, + }, + }); + + return object; +} + +async function hybridSearch(args: HybridSearchArgs) { + const { + semanticQuery, + keywordQuery, + workspaceId, + userId, + maxResults = 20, + selectedContextNodeIds = [], + } = args; + + const [nodeResults, documentResults] = await Promise.all([ + retrieveNodes( + { + semanticQuery, + keywordQuery, + originalQuery: semanticQuery || keywordQuery, + intent: 'retrieve', + }, + workspaceId, + userId, + maxResults, + selectedContextNodeIds + ), + retrieveDocuments( + { + semanticQuery, + keywordQuery, + originalQuery: semanticQuery || keywordQuery, + intent: 'retrieve', + }, + workspaceId, + userId, + maxResults, + selectedContextNodeIds + ), + ]); + + const merged = [...nodeResults, ...documentResults] + .slice(0, maxResults) + .map((md: any) => { + const meta = Array.isArray(md.metadata) ? (md.metadata[0] ?? {}) : {}; return { - finalAnswer: response.text, - additionalCitations: inputData.citations, - usedContext: true, + content: md.text || '', + type: meta.type ?? 'document', + sourceId: `${md.id}:${meta.chunkIndex ?? 0}`, + score: md.score ?? 0, + metadata: meta, }; - } catch (error) { - console.error('āŒ Contextual answer generation failed:', error); - throw error; + }); + + return { + results: merged, + totalFound: merged.length, + searchType: 'hybrid' as const, + }; +} + +function combineResults(results: any[]) { + const unique = new Map(); + results.forEach((r, i) => { + const key = r.sourceId as string; + if (!unique.has(key)) { + const positionScore = 1 / (i + 1); + // Remove recency boost here - it's already applied in combineAndScoreSearchResults + const combined = r.score * 0.8 + positionScore * 0.2; + unique.set(key, { ...r, score: Math.min(1, combined) }); } - }, -}); + }); + return Array.from(unique.values()) + .sort((a, b) => b.score - a.score) + .slice(0, 20); +} -const answerDirectStep = createStep({ - id: 'answer-direct-step', - description: 'Generate direct response using general knowledge', - inputSchema: intentClassificationOutputSchema, - outputSchema: answerOutputSchema, - execute: async ({ inputData, runtimeContext }) => { - const answerAgent = createAnswerAgent(); +async function rerankLLM(query: string, items: any[]) { + const schema = z.object({ scores: z.array(z.number().min(0).max(1)) }); - console.log('šŸ“ Generating direct response for general knowledge query'); + const preview = items + .map((it, i) => `[${i}] ${String(it.content).slice(0, 300)}`) + .join('\n\n'); - const prompt = `Answer this general knowledge question with a comprehensive, helpful response. + const { object } = await generateObject({ + model: ModelConfig.forReranking(), + schema, + prompt: ` +Query: "${query}" +Score each result from 0.0–1.0 for relevance. -**Question:** ${inputData.originalInput} +Scoring rubric: +- 50% Semantic match to the query intent. +- 25% Specificity (concrete details, matches entities). +- 15% Recency (newer is better). +- 10% Diversity (penalize near-duplicates). -**Instructions:** -- Provide accurate, detailed information -- Use examples and explanations where helpful -- Be educational and thorough -- Use clear, professional language -- Don't reference any workspace-specific information`; +You may use metadata if present: type, createdAt, author, chunkIndex. +Return only JSON: { "scores": [number...] } in the same order as provided. - try { - const response = await answerAgent.generate( - [{ role: 'user', content: prompt }], - { runtimeContext } - ); +Results: +${preview} +`, + experimental_telemetry: { + isEnabled: true, + functionId: 'rerank', + metadata: { stage: 'rerank', itemCount: items.length }, + }, + }); - console.log( - `āœ… Generated direct response (${response.text.length} characters)` - ); + return items + .map((it, i) => ({ + content: it.content, + sourceId: it.sourceId, + relevanceScore: object.scores[i] ?? 0.5, + type: it.type, + metadata: it.metadata, + })) + .sort((a, b) => b.relevanceScore - a.relevanceScore) + .slice(0, 20); +} - return { - finalAnswer: response.text, - additionalCitations: [], - usedContext: false, - }; - } catch (error) { - console.error('āŒ Direct answer generation failed:', error); - throw error; - } - }, -}); +async function answerWithContext( + userQuery: string, + ranked: any[], + { workspaceName, userName }: { workspaceName: string; userName: string } +) { + const context = ranked + .slice(0, 10) + .map((r, i) => `[Source ${i + 1}] ${String(r.content)}`) + .join('\n\n'); -const formatContextOutputStep = createStep({ - id: 'format-context-output-step', - description: 'Format the final assistant response output with context', - inputSchema: answerOutputSchema, - outputSchema: assistantWorkflowOutputSchema, - execute: async ({ inputData }) => { - console.log(`šŸ“‹ Formatting context-based response output`); + const { text } = await generateText({ + model: ModelConfig.forAssistant(), + system: `You are a precise, helpful assistant for the ${workspaceName} workspace. User: ${userName}. When you state a fact from context, add [Source N]. If unsure, say so briefly.`, + prompt: ` +User question: ${userQuery} - const response = { - finalAnswer: inputData.finalAnswer, - citations: inputData.additionalCitations || [], - searchPerformed: inputData.usedContext, - }; +Use only the context below. If the answer requires assumptions not supported by the context, say what is missing. +Prefer bullet points for lists. Include dates and names when available. - console.log(`āœ… Formatted response with context`); - console.log( - `šŸ“Š Search performed: ${response.searchPerformed ? 'Yes' : 'No'}` - ); - console.log( - `šŸ’¬ Response length: ${response.finalAnswer.length} characters` - ); +Context: +${context} +`, + experimental_telemetry: { + isEnabled: true, + functionId: 'answer-with-context', + metadata: { + stage: 'answer', + withContext: true, + contextItems: ranked.length, + }, + }, + }); - return response; - }, -}); + const used = new Set(); + for (const m of text.matchAll(/\[Source\s+(\d+)\]/gi)) used.add(Number(m[1])); + const citations = Array.from(used) + .slice(0, 10) + .map((i) => { + const r = ranked[i - 1]; + return r + ? { + sourceId: r.sourceId, + quote: + String(r.content).slice(0, 200).trim() + + (String(r.content).length > 200 ? '...' : ''), + } + : null; + }) + .filter(Boolean) as { sourceId: string; quote: string }[]; -const formatDirectOutputStep = createStep({ - id: 'format-direct-output-step', - description: 'Format the final assistant direct response output', - inputSchema: answerOutputSchema, - outputSchema: assistantWorkflowOutputSchema, - execute: async ({ inputData }) => { - console.log(`šŸ“‹ Formatting direct response output (no context used)`); + return { answer: text, citations }; +} - const response = { - finalAnswer: inputData.finalAnswer, - citations: inputData.additionalCitations || [], - searchPerformed: false, - }; +async function answerDirect( + userQuery: string, + { workspaceName, userName }: { workspaceName: string; userName: string } +) { + const { text } = await generateText({ + model: ModelConfig.forAssistant(), + system: `You are a precise, helpful assistant. Keep answers concise but complete. You are working in the ${workspaceName} workspace. User: ${userName}.`, + prompt: `Answer the question clearly. If you need extra context from the workspace, say what would help. +Question: ${userQuery}`, + experimental_telemetry: { + isEnabled: true, + functionId: 'answer-direct', + metadata: { stage: 'answer', withContext: false }, + }, + }); - console.log(`āœ… Formatted direct response`); - console.log( - `šŸ’¬ Response length: ${response.finalAnswer.length} characters` - ); - - return response; - }, -}); - -export const assistantWorkflow = createWorkflow({ - id: 'assistant-workflow', - description: - 'Declarative AI assistant workflow optimized for smaller LLMs with proper branching', - inputSchema: assistantWorkflowInputSchema, - outputSchema: assistantWorkflowOutputSchema, -}) - // Step 1: Always classify intent first - .then(intentClassificationStep) - - // Step 2: Branch based on intent classification - .branch([ - // RETRIEVE BRANCH: RAG pipeline for workspace-specific queries - [ - async ({ inputData }) => inputData.intent === 'retrieve', - createWorkflow({ - id: 'retrieve-branch', - inputSchema: intentClassificationOutputSchema, - outputSchema: assistantWorkflowOutputSchema, - }) - .then(queryRewriteStep) // Optimize query for search - .then(runSearchesStep) // Execute parallel searches - .then(combineResultsStep) // Combine results algorithmically - .then(rerankStep) // LLM-based reranking - .then(answerWithContextStep) // Generate answer with context - .then(formatContextOutputStep) // Format output - .commit(), - ], - - // NO_CONTEXT BRANCH: Direct answer for general knowledge queries - [ - async ({ inputData }) => inputData.intent === 'no_context', - createWorkflow({ - id: 'no-context-branch', - inputSchema: intentClassificationOutputSchema, - outputSchema: assistantWorkflowOutputSchema, - }) - .then(answerDirectStep) // Generate direct answer - .then(formatDirectOutputStep) // Format output - .commit(), - ], - ]) - .commit(); + return text; +} diff --git a/apps/server/src/lib/ai/chunking.ts b/apps/server/src/lib/ai/chunking.ts index 928d4d77..64ae6133 100644 --- a/apps/server/src/lib/ai/chunking.ts +++ b/apps/server/src/lib/ai/chunking.ts @@ -1,8 +1,8 @@ -import { MDocument } from '@mastra/rag'; +import { generateText } from 'ai'; import type { NodeType } from '@colanode/core'; import { config } from '@colanode/server/lib/config'; import { TextChunk } from '@colanode/server/types/chunking'; -import { createChunkEnrichmentAgent } from './ai-agents'; +import { ModelConfig } from './ai-models'; export const chunkText = async ( text: string, @@ -15,20 +15,20 @@ export const chunkText = async ( const chunkSize = config.ai.chunking.defaultChunkSize; const chunkOverlap = config.ai.chunking.defaultOverlap; - - const doc = MDocument.fromText(text); - const docChunks = await doc.chunk({ - strategy: 'recursive', - maxSize: chunkSize, - overlap: chunkOverlap, + const docChunks = await recursiveCharacterSplit(text, { + chunkSize, + chunkOverlap, + keepSeparator: true, }); - const chunks = docChunks - .map((chunk) => ({ text: chunk.text })) - .filter((c) => c.text.trim().length > 5); + const chunks: TextChunk[] = docChunks + .map((c) => ({ text: c })) + .filter((c: TextChunk) => c.text.trim().length > 5); + + console.log('chunks', chunks); if (!config.ai.chunking.enhanceWithContext) { - return chunks; + return chunks; // return plain chunks without summaries } const enrichedChunks: TextChunk[] = []; @@ -56,10 +56,8 @@ const enrichChunk = async ( nodeType: NodeType ): Promise => { try { - const enrichmentAgent = createChunkEnrichmentAgent(); - const prompt = ` -Generate a concise summary of the following text chunk that is part of a larger document. +Generate a concise summary of the following text chunk that is part of a larger document. This summary will be used to enhance vector search retrieval by providing additional context about this specific chunk. @@ -92,13 +90,170 @@ ${chunk} Provide only the summary with no additional commentary or explanations. `; - const response = await enrichmentAgent.generate([ - { role: 'user', content: prompt }, - ]); + const { text } = await generateText({ + model: ModelConfig.forContextEnhancer(), + prompt, + experimental_telemetry: { + isEnabled: true, + functionId: 'chunk-enrichment', + metadata: { + stage: 'chunk-enrichment', + nodeType, + chunkLength: chunk.length, + }, + }, + }); - return response.text.trim(); + return text.trim(); } catch (error) { console.warn('Failed to enrich chunk:', error); return undefined; } }; + +/** + * Recursive character splitter. + * - Ordered separators: ["\n\n", "\n", " ", ""] + * - keepSeparator: when true, boundaries are preserved on the next chunk + * - chunkOverlap is applied during merge + * + */ +export async function recursiveCharacterSplit( + text: string, + { + chunkSize, + chunkOverlap = 0, + keepSeparator = true, + separators = ['\n\n', '\n', ' ', ''], + lengthFn, + }: { + chunkSize: number; + chunkOverlap?: number; + keepSeparator?: boolean; + separators?: string[]; + lengthFn?: (s: string) => number | Promise; + } +): Promise { + if (chunkSize <= 0) throw new Error('chunkSize must be > 0'); + if (chunkOverlap < 0) throw new Error('chunkOverlap must be >= 0'); + if (chunkOverlap >= chunkSize) { + throw new Error('Cannot have chunkOverlap >= chunkSize'); + } + + const len = async (s: string) => + (lengthFn ? await lengthFn(s) : s.length) as number; + + const escapeRegExp = (lit: string) => + lit.replace(/[\/\\^$*+?.()|[\]{}-]/g, '\\$&'); + + const splitOnSeparator = (t: string, sep: string, keep: boolean) => { + let splits: string[]; + if (sep) { + if (keep) { + const re = new RegExp(`(?=${escapeRegExp(sep)})`, 'g'); + splits = t.split(re); + } else { + splits = t.split(sep); + } + } else { + splits = t.split(''); + } + return splits.filter((s) => s !== ''); + }; + + const joinDocs = (docs: string[], sep: string): string | null => { + const joined = docs.join(sep).trim(); + return joined === '' ? null : joined; + }; + + const mergeSplits = async (splits: string[], sep: string) => { + const out: string[] = []; + const cur: string[] = []; + let total = 0; + + for (const d of splits) { + const l = await len(d); + + if (total + l + cur.length * sep.length > chunkSize) { + if (total > chunkSize) { + // parity with LC: warn but continue + console.warn( + `Created a chunk of size ${total}, which is longer than the specified ${chunkSize}` + ); + } + if (cur.length > 0) { + const doc = joinDocs(cur, sep); + if (doc !== null) out.push(doc); + + // pop from the left until overlap satisfied or still too big + while ( + total > chunkOverlap || + (total + l + cur.length * sep.length > chunkSize && total > 0) + ) { + total -= await len(cur[0]!); + cur.shift(); + } + } + } + + cur.push(d); + total += l; + } + + const doc = joinDocs(cur, sep); + if (doc !== null) out.push(doc); + return out; + }; + + const _split = async (t: string, seps: string[]): Promise => { + const finalChunks: string[] = []; + + // choose separator: first present wins, else last fallback + let sep = seps[seps.length - 1] || ''; + let nextSeps: string[] | undefined; + for (let i = 0; i < seps.length; i++) { + const s = seps[i]; + if (s === undefined) continue; + if (s === '') { + sep = s; + break; + } + if (t.includes(s)) { + sep = s; + nextSeps = seps.slice(i + 1); + break; + } + } + + const splits = splitOnSeparator(t, sep, keepSeparator); + const mergeSep = keepSeparator ? '' : sep; + + let good: string[] = []; + for (const s of splits) { + if ((await len(s)) < chunkSize) { + good.push(s); + } else { + if (good.length) { + const merged = await mergeSplits(good, mergeSep); + finalChunks.push(...merged); + good = []; + } + if (!nextSeps) { + finalChunks.push(s); // no finer separators left + } else { + const rec = await _split(s, nextSeps); + finalChunks.push(...rec); + } + } + } + + if (good.length) { + const merged = await mergeSplits(good, mergeSep); + finalChunks.push(...merged); + } + + return finalChunks; + }; + + return _split(text, separators); +} diff --git a/apps/server/src/lib/ai/document-retrievals.ts b/apps/server/src/lib/ai/document-retrievals.ts index da17e142..03ac18eb 100644 --- a/apps/server/src/lib/ai/document-retrievals.ts +++ b/apps/server/src/lib/ai/document-retrievals.ts @@ -1,28 +1,22 @@ -import { MDocument } from '@mastra/rag'; -import { createOpenAI } from '@ai-sdk/openai'; +import { AIDocument } from '@colanode/server/lib/ai/utils'; import { embed } from 'ai'; import { sql } from 'kysely'; import { database } from '@colanode/server/data/database'; import { combineAndScoreSearchResults } from '@colanode/server/lib/ai/utils'; import { config } from '@colanode/server/lib/config'; +import { getEmbeddingModel } from '@colanode/server/lib/ai/ai-models'; import { QueryRewriteOutput } from '@colanode/server/types/ai'; import { SearchResult } from '@colanode/server/types/retrieval'; -const embeddingModel = config.ai.enabled - ? createOpenAI({ apiKey: config.ai.embedding.apiKey }).embedding( - config.ai.embedding.modelName - ) - : undefined; - export const retrieveDocuments = async ( rewrittenQuery: QueryRewriteOutput, workspaceId: string, userId: string, limit?: number, contextNodeIds?: string[] -): Promise => { - if (!config.ai.enabled || !embeddingModel) { +): Promise => { + if (!config.ai.enabled) { return []; } @@ -33,9 +27,15 @@ export const retrieveDocuments = async ( let semanticResults: SearchResult[] = []; if (doSemantic) { + const embeddingModel = getEmbeddingModel(); const { embedding } = await embed({ model: embeddingModel, value: rewrittenQuery.semanticQuery, + providerOptions: { + openai: { + dimensions: config.ai.embedding.dimensions, + }, + }, }); if (embedding) { semanticResults = await semanticSearchDocuments( @@ -85,7 +85,7 @@ const semanticSearchDocuments = async ( 'documents.created_at', 'documents.created_by', 'document_embeddings.chunk as chunk_index', - sql`${sql.raw(`'[${embedding}]'::vector`)} <=> document_embeddings.embedding_vector`.as( + sql`1 - (${sql.raw(`'[${embedding}]'::vector`)} <=> document_embeddings.embedding_vector)`.as( 'similarity' ), ]) @@ -100,18 +100,21 @@ const semanticSearchDocuments = async ( } const results = await queryBuilder - .groupBy([ + .distinctOn([ 'document_embeddings.document_id', - 'document_embeddings.text', - 'document_embeddings.summary', - 'documents.created_at', - 'documents.created_by', 'document_embeddings.chunk', ]) - .orderBy('similarity', 'asc') + .orderBy('document_embeddings.document_id') + .orderBy('document_embeddings.chunk') + .orderBy('similarity', 'desc') .limit(limit) .execute(); + console.log( + 'results text', + results.map((r) => r.text) + ); + return results.map((result) => ({ id: result.id, text: result.text, @@ -196,8 +199,8 @@ const keywordSearchDocuments = async ( const combineSearchResults = async ( semanticResults: SearchResult[], keywordResults: SearchResult[] -): Promise => { - if (!config.ai.enabled || !embeddingModel) { +): Promise => { + if (!config.ai.enabled) { return []; } diff --git a/apps/server/src/lib/ai/node-retrievals.ts b/apps/server/src/lib/ai/node-retrievals.ts index 6ea01b7d..08013130 100644 --- a/apps/server/src/lib/ai/node-retrievals.ts +++ b/apps/server/src/lib/ai/node-retrievals.ts @@ -1,28 +1,22 @@ -import { MDocument } from '@mastra/rag'; -import { createOpenAI } from '@ai-sdk/openai'; +import { AIDocument } from '@colanode/server/lib/ai/utils'; import { embed } from 'ai'; import { sql } from 'kysely'; import { database } from '@colanode/server/data/database'; import { combineAndScoreSearchResults } from '@colanode/server/lib/ai/utils'; import { config } from '@colanode/server/lib/config'; +import { getEmbeddingModel } from '@colanode/server/lib/ai/ai-models'; import { QueryRewriteOutput } from '@colanode/server/types/ai'; import { SearchResult } from '@colanode/server/types/retrieval'; -const embeddingModel = config.ai.enabled - ? createOpenAI({ apiKey: config.ai.embedding.apiKey }).embedding( - config.ai.embedding.modelName - ) - : undefined; - export const retrieveNodes = async ( rewrittenQuery: QueryRewriteOutput, workspaceId: string, userId: string, limit?: number, contextNodeIds?: string[] -): Promise => { - if (!config.ai.enabled || !embeddingModel) { +): Promise => { + if (!config.ai.enabled) { return []; } @@ -33,9 +27,15 @@ export const retrieveNodes = async ( let semanticResults: SearchResult[] = []; if (doSemantic) { + const embeddingModel = getEmbeddingModel(); const { embedding } = await embed({ model: embeddingModel, value: rewrittenQuery.semanticQuery, + providerOptions: { + openai: { + dimensions: config.ai.embedding.dimensions, + }, + }, }); if (embedding) { semanticResults = await semanticSearchNodes( @@ -84,7 +84,7 @@ const semanticSearchNodes = async ( 'nodes.created_at', 'nodes.created_by', 'node_embeddings.chunk as chunk_index', - sql`${sql.raw(`'[${embedding}]'::vector`)} <=> node_embeddings.embedding_vector`.as( + sql`1 - (${sql.raw(`'[${embedding}]'::vector`)} <=> node_embeddings.embedding_vector)`.as( 'similarity' ), ]) @@ -99,15 +99,10 @@ const semanticSearchNodes = async ( } const results = await queryBuilder - .groupBy([ - 'node_embeddings.node_id', - 'node_embeddings.text', - 'nodes.created_at', - 'nodes.created_by', - 'node_embeddings.chunk', - 'node_embeddings.summary', - ]) - .orderBy('similarity', 'asc') + .distinctOn(['node_embeddings.node_id', 'node_embeddings.chunk']) + .orderBy('node_embeddings.node_id') + .orderBy('node_embeddings.chunk') + .orderBy('similarity', 'desc') .limit(limit) .execute(); @@ -194,8 +189,8 @@ const keywordSearchNodes = async ( const combineSearchResults = async ( semanticResults: SearchResult[], keywordResults: SearchResult[] -): Promise => { - if (!config.ai.enabled || !embeddingModel) { +): Promise => { + if (!config.ai.enabled) { return []; } diff --git a/apps/server/src/lib/ai/old-prompts.ts b/apps/server/src/lib/ai/old-prompts.ts deleted file mode 100644 index 0041d5e7..00000000 --- a/apps/server/src/lib/ai/old-prompts.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { PromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts'; - -export const queryRewritePrompt = PromptTemplate.fromTemplate( - ` - You are an expert at rewriting search queries to optimize for both semantic similarity and keyword-based search in a document retrieval system. - Your task is to generate two separate optimized queries: - 1. A semantic search query optimized for vector embeddings and semantic similarity - 2. A keyword search query optimized for full-text search using PostgreSQL's tsquery - - - - For semantic search query: - 1. Focus on conceptual meaning and intent - 2. Include context-indicating terms - 3. Preserve relationship words between concepts - 4. Expand concepts with related terms - 5. Remove noise words and syntax-specific terms - - For keyword search query: - 1. Focus on specific technical terms and exact matches - 2. Include variations of key terms - 3. Keep proper nouns and domain-specific vocabulary - 4. Optimize for PostgreSQL's websearch_to_tsquery syntax - 5. Include essential filters and constraints - - - - Original query: {query} - - - - Return a JSON object with: - {{ - "semanticQuery": "optimized query for semantic search", - "keywordQuery": "optimized query for keyword search" -}} -` -); - -export const summarizationPrompt = PromptTemplate.fromTemplate( - ` - Summarize the following text focusing on key points relevant to the user's query. - If the text is short (<100 characters), return it as is. - - - - Text: {text} - User Query: {query} -` -); - -export const rerankPrompt = PromptTemplate.fromTemplate( - ` - You are the final relevance judge in a hybrid search system. Your task is to re-rank search results by analyzing their true relevance to the user's query. - These documents have already passed through: - 1. Semantic search (vector similarity) - 2. Keyword-based search (full-text search) - - Your ranking will determine the final order and which documents are shown to the user. - - - - Each document contains: - - Main content text - - Optional summary/context - - Metadata (type, creation info) - The documents can be: - - Workspace nodes (various content types) - - Documents (files, notes) - - Database records - - - - Evaluate relevance based on: - 1. Direct answer presence (highest priority) - - Does the content directly answer the query? - - Are key details or facts present? - - 2. Contextual relevance - - How well does the content relate to the query topic? - - Is the context/summary relevant? - - Does it provide important background information? - - 3. Information freshness - - For time-sensitive queries, prefer recent content - - For conceptual queries, recency matters less - - 4. Content completeness - - Does it provide comprehensive information? - - Are related concepts explained? - - 5. Source appropriateness - - Is the document type appropriate for the query? - - Does the source authority match the information need? - - - - Score from 0 to 1, where: - 1.0: Perfect match, directly answers query - 0.8-0.9: Highly relevant, contains most key information - 0.5-0.7: Moderately relevant, contains some useful information - 0.2-0.4: Tangentially relevant, minimal useful information - 0.0-0.1: Not relevant or useful for the query - - - - {context} - - - - {query} - - - - Return a JSON array of objects, each containing: - - "index": original position (integer) - - "score": relevance score (0-1 float) - - "type": document type (string) - - "sourceId": original source ID (string) - - Example: - [ - {{"index": 2, "score": 0.95, "type": "document", "sourceId": "doc123"}}, - {{"index": 0, "score": 0.7, "type": "node", "sourceId": "node456"}} - ] -` -); - -export const answerPrompt = ChatPromptTemplate.fromTemplate( - ` - You are an AI assistant in a collaboration workspace app called Colanode. - - CURRENT TIME: {currentTimestamp} - WORKSPACE: {workspaceName} - USER: {userName} ({userEmail}) - - - - {formattedChatHistory} - - - - {formattedDocuments} - - - - {question} - - - - Based solely on the current conversation history and the relevant context above, provide a clear and professional answer to the user's query. - - Pay attention to the metadata provided for each source (like creation date, author, path, document type, etc.) to: - - Properly attribute information to its source - - Consider the recency and relevance of the information - - Understand the relationships between different content pieces - - Recognize the context in which content was created or modified - - In your answer, include exact quotes from the provided context that support your answer. - If the relevant context does not contain any information that answers the user's query, respond with "No relevant information found." This is a critical step to ensure correct answers. - - - - Return your response as a JSON object with the following structure: - {{ - "answer": , - "citations": [ - {{ "sourceId": , "quote": }}, - ... - ] - }} -` -); - -export const intentRecognitionPrompt = PromptTemplate.fromTemplate( - ` - Determine if the following user query requires retrieving context from the workspace's knowledge base. - You are a crucial decision point in an AI assistant system that must decide between: - 1. Retrieving and using specific context from the workspace ("retrieve") - 2. Answering directly from general knowledge ("no_context") - - - - This system has access to: - - Documents and their embeddings - - Node content (various types of workspace items) - - Database records and their fields - - Previous conversation history - - - - Return "retrieve" when the query: - - Asks about specific workspace content, documents, or data - - References previous conversations or shared content - - Mentions specific projects, tasks, or workspace items - - Requires up-to-date information from the workspace - - Contains temporal references to workspace activity - - Asks about specific people or collaborators - - Needs details about database records or fields - - Return "no_context" when the query: - - Asks for general knowledge or common facts - - Requests simple calculations or conversions - - Asks about general concepts without workspace specifics - - Makes small talk - - Requests explanations of universal concepts - - Can be answered correctly without workspace-specific information - - - - "retrieve" examples: - - "What did John say about the API design yesterday?" - - "Show me the latest documentation about user authentication" - - "Find records in the Projects database where status is completed" - - "What were the key points from our last meeting?" - - "no_context" examples: - - "What is REST API?" - - "How do I write a good commit message?" - - "Convert 42 kilometers to miles" - - "What's your name?" - - "Explain what is Docker in simple terms" - - - - {formattedChatHistory} - - - - {question} - - - - Return exactly one value: "retrieve" or "no_context" -` -); - -export const noContextPrompt = PromptTemplate.fromTemplate( - ` - Answer the following query concisely using general knowledge, without retrieving additional context. Return only the answer. - - - - {formattedChatHistory} - - - - {question} -` -); - -export const databaseFilterPrompt = ChatPromptTemplate.fromTemplate( - ` - You are an expert at analyzing natural language queries and converting them into structured database filters. - - Your task is to: - 1. Determine if this query is asking or makes sense to answer by filtering/searching databases - 2. If yes, generate appropriate filter attributes for each relevant database - 3. If no, return shouldFilter: false - - - - Available Databases: - {databasesInfo} - - - - {query} - - - - Only include databases that are relevant to the query. - For each filter, use the exact field IDs from the database schema. - Use appropriate operators based on field types. - - - - Return a JSON object with: - - shouldFilter: boolean - - filters: array of objects with: - - databaseId: string - - filters: array of DatabaseViewFilterAttributes - - Example Response: - {{ - "shouldFilter": true, - "filters": [ - {{ - "databaseId": "db1", - "filters": [ - {{ - "type": "field", - "fieldId": "field1", - "operator": "contains", - "value": "search term" - }} - ] - }} - ] - }} -` -); - -export const chunkSummarizationPrompt = PromptTemplate.fromTemplate( - ` - Generate a concise summary of the following text chunk that is part of a larger document. - This summary will be used to enhance vector search retrieval by providing additional context about this specific chunk. - - - - Content Type: {nodeType} - - - - 1. Create a brief (30-50 words) summary that captures the key points and main idea of the chunk - 2. Consider how this chunk fits into the overall document provided - 3. If the chunk appears to be part of a specific section, identify its role or purpose - 4. If the chunk contains structured data (like a database record), identify the type of information it represents - 5. Use neutral, descriptive language - 6. Consider the content type ("{nodeType}") when creating the summary - different types have different purposes: - - "message": Communication content in a conversation - - "page": Document-like content with structured information - - "record": Database record with specific fields and values - - Other types: Adapt your summary accordingly - - - - {fullText} - - - - {chunk} - - - - Provide only the summary with no additional commentary or explanations. -` -); diff --git a/apps/server/src/lib/ai/utils.ts b/apps/server/src/lib/ai/utils.ts index 99cc44fe..a1bfe93a 100644 --- a/apps/server/src/lib/ai/utils.ts +++ b/apps/server/src/lib/ai/utils.ts @@ -1,4 +1,4 @@ -import { MDocument } from '@mastra/rag'; +import ms from 'ms'; import { SearchResult } from '@colanode/server/types/retrieval'; export const formatDate = (date?: Date | null): string => { @@ -33,8 +33,8 @@ const processSearchResult = ( const key = createKey(result); const recencyBoost = calculateRecencyBoost(result.createdAt); const normalizedScore = isKeyword - ? (result.score / maxScore) * weight - : ((maxScore - result.score) / maxScore) * weight; + ? (result.score / maxScore) * weight // rank normalized + : Math.max(0, Math.min(1, result.score)) * weight; // similarity already 0..1 if (combined.has(key)) { const existing = combined.get(key)!; @@ -47,27 +47,12 @@ const processSearchResult = ( } }; -const createDocumentFromResult = ( - result: SearchResult & { finalScore: number }, - authorMap: Map -): MDocument => { - const author = result.createdBy ? authorMap.get(result.createdBy) : null; - const summaryPart = (result.summary ?? '').trim(); - const content = summaryPart - ? `${summaryPart}\n\n${result.text}` - : result.text; - - const doc = MDocument.fromText(content, { - id: result.id, - score: result.finalScore, - createdAt: result.createdAt, - - type: (result as any).sourceType ?? 'document', - chunkIndex: result.chunkIndex, - author: author ? { id: author.id, name: author.name || 'Unknown' } : null, - }); - - return doc; +export type AIDocument = { + id: string; + text: string; + summary: string | null; + score: number; + metadata: Record[]; }; export const combineAndScoreSearchResults = ( @@ -76,7 +61,7 @@ export const combineAndScoreSearchResults = ( semanticSearchWeight: number, keywordSearchWeight: number, authorMap: Map -): Promise => { +): Promise => { const maxSemanticScore = Math.max(...semanticResults.map((r) => r.score), 1); const maxKeywordScore = Math.max(...keywordResults.map((r) => r.score), 1); @@ -103,6 +88,26 @@ export const combineAndScoreSearchResults = ( return Promise.resolve( Array.from(combined.values()) .sort((a, b) => b.finalScore - a.finalScore) - .map((result) => createDocumentFromResult(result, authorMap)) + .map((result) => { + const author = result.createdBy + ? authorMap.get(result.createdBy) + : null; + const type = (result as any).sourceType ?? 'document'; + const meta: Record = { + type, + createdAt: result.createdAt ?? null, + createdBy: result.createdBy ?? null, + chunkIndex: result.chunkIndex, + }; + if (author) + meta.author = { id: author.id, name: author.name || 'Unknown' }; + return { + id: result.id, + text: result.text, + summary: result.summary, + score: result.finalScore, + metadata: [meta], + }; + }) ); }; diff --git a/apps/server/src/lib/config/ai.ts b/apps/server/src/lib/config/ai.ts index cc577a52..a4a9beba 100644 --- a/apps/server/src/lib/config/ai.ts +++ b/apps/server/src/lib/config/ai.ts @@ -87,91 +87,91 @@ export const aiConfigSchema = z.discriminatedUnion('enabled', [ export type AiConfig = z.infer; export const readAiConfigVariables = () => { - return { - enabled: false, - }; - // return { - // enabled: process.env.AI_ENABLED === 'true', - // nodeEmbeddingDelay: process.env.AI_NODE_EMBEDDING_DELAY, - // documentEmbeddingDelay: process.env.AI_DOCUMENT_EMBEDDING_DELAY, - // providers: { - // openai: { - // apiKey: process.env.OPENAI_API_KEY, - // enabled: process.env.OPENAI_ENABLED, - // }, - // google: { - // apiKey: process.env.GOOGLE_API_KEY, - // enabled: process.env.GOOGLE_ENABLED, - // }, - // }, - // langfuse: { - // enabled: process.env.LANGFUSE_ENABLED, - // publicKey: process.env.LANGFUSE_PUBLIC_KEY, - // secretKey: process.env.LANGFUSE_SECRET_KEY, - // baseUrl: process.env.LANGFUSE_BASE_URL, - // }, - // models: { - // queryRewrite: { - // provider: process.env.QUERY_REWRITE_PROVIDER, - // modelName: process.env.QUERY_REWRITE_MODEL, - // temperature: process.env.QUERY_REWRITE_TEMPERATURE, - // }, - // response: { - // provider: process.env.RESPONSE_PROVIDER, - // modelName: process.env.RESPONSE_MODEL, - // temperature: process.env.RESPONSE_TEMPERATURE, - // }, - // rerank: { - // provider: process.env.RERANK_PROVIDER, - // modelName: process.env.RERANK_MODEL, - // temperature: process.env.RERANK_TEMPERATURE, - // }, - // summarization: { - // provider: process.env.SUMMARIZATION_PROVIDER, - // modelName: process.env.SUMMARIZATION_MODEL, - // temperature: process.env.SUMMARIZATION_TEMPERATURE, - // }, - // contextEnhancer: { - // provider: process.env.CHUNK_CONTEXT_PROVIDER, - // modelName: process.env.CHUNK_CONTEXT_MODEL, - // temperature: process.env.CHUNK_CONTEXT_TEMPERATURE, - // }, - // noContext: { - // provider: process.env.NO_CONTEXT_PROVIDER, - // modelName: process.env.NO_CONTEXT_MODEL, - // temperature: process.env.NO_CONTEXT_TEMPERATURE, - // }, - // intentRecognition: { - // provider: process.env.INTENT_RECOGNITION_PROVIDER, - // modelName: process.env.INTENT_RECOGNITION_MODEL, - // temperature: process.env.INTENT_RECOGNITION_TEMPERATURE, - // }, - // databaseFilter: { - // provider: process.env.DATABASE_FILTER_PROVIDER, - // modelName: process.env.DATABASE_FILTER_MODEL, - // temperature: process.env.DATABASE_FILTER_TEMPERATURE, - // }, - // }, - // embedding: { - // provider: process.env.EMBEDDING_PROVIDER, - // modelName: process.env.EMBEDDING_MODEL, - // dimensions: process.env.EMBEDDING_DIMENSIONS, - // apiKey: process.env.EMBEDDING_API_KEY, - // batchSize: process.env.EMBEDDING_BATCH_SIZE, - // }, - // chunking: { - // defaultChunkSize: process.env.CHUNK_DEFAULT_CHUNK_SIZE, - // defaultOverlap: process.env.CHUNK_DEFAULT_OVERLAP, - // enhanceWithContext: process.env.CHUNK_ENHANCE_WITH_CONTEXT, - // }, - // retrieval: { - // hybridSearch: { - // semanticSearchWeight: - // process.env.RETRIEVAL_HYBRID_SEARCH_SEMANTIC_WEIGHT, - // keywordSearchWeight: process.env.RETRIEVAL_HYBRID_SEARCH_KEYWORD_WEIGHT, - // maxResults: process.env.RETRIEVAL_HYBRID_SEARCH_MAX_RESULTS, - // }, - // }, + // enabled: true, // }; + + return { + enabled: process.env.AI_ENABLED === 'true', + nodeEmbeddingDelay: process.env.AI_NODE_EMBEDDING_DELAY, + documentEmbeddingDelay: process.env.AI_DOCUMENT_EMBEDDING_DELAY, + providers: { + openai: { + apiKey: process.env.OPENAI_API_KEY, + enabled: process.env.OPENAI_ENABLED, + }, + google: { + apiKey: process.env.GOOGLE_API_KEY, + enabled: process.env.GOOGLE_ENABLED, + }, + }, + langfuse: { + enabled: process.env.LANGFUSE_ENABLED, + publicKey: process.env.LANGFUSE_PUBLIC_KEY, + secretKey: process.env.LANGFUSE_SECRET_KEY, + baseUrl: process.env.LANGFUSE_BASE_URL, + }, + models: { + queryRewrite: { + provider: process.env.QUERY_REWRITE_PROVIDER, + modelName: process.env.QUERY_REWRITE_MODEL, + temperature: process.env.QUERY_REWRITE_TEMPERATURE, + }, + response: { + provider: process.env.RESPONSE_PROVIDER, + modelName: process.env.RESPONSE_MODEL, + temperature: process.env.RESPONSE_TEMPERATURE, + }, + rerank: { + provider: process.env.RERANK_PROVIDER, + modelName: process.env.RERANK_MODEL, + temperature: process.env.RERANK_TEMPERATURE, + }, + summarization: { + provider: process.env.SUMMARIZATION_PROVIDER, + modelName: process.env.SUMMARIZATION_MODEL, + temperature: process.env.SUMMARIZATION_TEMPERATURE, + }, + contextEnhancer: { + provider: process.env.CHUNK_CONTEXT_PROVIDER, + modelName: process.env.CHUNK_CONTEXT_MODEL, + temperature: process.env.CHUNK_CONTEXT_TEMPERATURE, + }, + noContext: { + provider: process.env.NO_CONTEXT_PROVIDER, + modelName: process.env.NO_CONTEXT_MODEL, + temperature: process.env.NO_CONTEXT_TEMPERATURE, + }, + intentRecognition: { + provider: process.env.INTENT_RECOGNITION_PROVIDER, + modelName: process.env.INTENT_RECOGNITION_MODEL, + temperature: process.env.INTENT_RECOGNITION_TEMPERATURE, + }, + databaseFilter: { + provider: process.env.DATABASE_FILTER_PROVIDER, + modelName: process.env.DATABASE_FILTER_MODEL, + temperature: process.env.DATABASE_FILTER_TEMPERATURE, + }, + }, + embedding: { + provider: process.env.EMBEDDING_PROVIDER, + modelName: process.env.EMBEDDING_MODEL, + dimensions: process.env.EMBEDDING_DIMENSIONS, + apiKey: process.env.EMBEDDING_API_KEY, + batchSize: process.env.EMBEDDING_BATCH_SIZE, + }, + chunking: { + defaultChunkSize: process.env.CHUNK_DEFAULT_CHUNK_SIZE, + defaultOverlap: process.env.CHUNK_DEFAULT_OVERLAP, + enhanceWithContext: process.env.CHUNK_ENHANCE_WITH_CONTEXT, + }, + retrieval: { + hybridSearch: { + semanticSearchWeight: + process.env.RETRIEVAL_HYBRID_SEARCH_SEMANTIC_WEIGHT, + keywordSearchWeight: process.env.RETRIEVAL_HYBRID_SEARCH_KEYWORD_WEIGHT, + maxResults: process.env.RETRIEVAL_HYBRID_SEARCH_MAX_RESULTS, + }, + }, + }; }; diff --git a/apps/server/src/lib/observability/otel.ts b/apps/server/src/lib/observability/otel.ts new file mode 100644 index 00000000..d6bc6140 --- /dev/null +++ b/apps/server/src/lib/observability/otel.ts @@ -0,0 +1,36 @@ +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; +import { LangfuseExporter } from 'langfuse-vercel'; + +let sdk: NodeSDK | null = null; +let started = false; + +export const initObservability = () => { + if (started) return; + + const secretKey = process.env.LANGFUSE_SECRET_KEY; + const publicKey = process.env.LANGFUSE_PUBLIC_KEY; + const baseUrl = process.env.LANGFUSE_BASEURL; + + if (!secretKey || !publicKey) { + return; + } + + console.log('LANGFUSE_PUBLIC_KEY', publicKey); + console.log('LANGFUSE_BASEURL', baseUrl); + + const exporter = new LangfuseExporter({ + secretKey, + publicKey, + baseUrl: baseUrl || 'https://cloud.langfuse.com', + }); + + sdk = new NodeSDK({ + traceExporter: exporter, + instrumentations: [getNodeAutoInstrumentations()], + }); + + sdk.start(); + + started = true; +}; diff --git a/apps/server/src/services/assistant-trigger.ts b/apps/server/src/services/assistant-trigger.ts new file mode 100644 index 00000000..be8ca563 --- /dev/null +++ b/apps/server/src/services/assistant-trigger.ts @@ -0,0 +1,42 @@ +import { eventBus } from '@colanode/server/lib/event-bus'; +import { fetchNode } from '@colanode/server/lib/nodes'; +import { createLogger } from '@colanode/server/lib/logger'; +import { jobService } from '@colanode/server/services/job-service'; + +const logger = createLogger('server:service:assistant-trigger'); + +export const initAssistantTrigger = () => { + eventBus.subscribe(async (event) => { + if (event.type !== 'node.created') { + return; + } + + try { + const node = await fetchNode(event.nodeId); + if (!node) { + return; + } + + const attributes = node.attributes as { + type?: string; + subtype?: string; + } | null; + if ( + attributes?.type === 'message' && + attributes?.subtype === 'question' + ) { + await jobService.addJob({ + type: 'assistant.respond', + messageId: node.id, + workspaceId: event.workspaceId, + }); + + logger.debug( + `Queued assistant response for question message ${node.id} (workspace ${event.workspaceId})` + ); + } + } catch (error) { + logger.error(error, 'Failed handling node.created for assistant trigger'); + } + }); +}; diff --git a/apps/server/src/services/job-service.ts b/apps/server/src/services/job-service.ts index c91d77aa..7a606bcf 100644 --- a/apps/server/src/services/job-service.ts +++ b/apps/server/src/services/job-service.ts @@ -20,7 +20,9 @@ class JobService { private readonly prefix = `{${config.redis.jobs.prefix}}`; public async initQueue(): Promise { + console.log('initQueue'); if (this.jobQueue) { + console.log('jobQueue already initialized'); return; } @@ -39,11 +41,14 @@ class JobService { logger.error(error, `Job queue error`); }); + console.log('initRecurringJobs'); await this.initRecurringJobs(); } public async initWorker() { + console.log('initWorker'); if (this.jobWorker) { + console.log('jobWorker already initialized'); return; } @@ -61,9 +66,38 @@ class JobService { throw new Error('Job queue not initialized.'); } + console.log('adding job', job.type); await this.jobQueue.add(job.type, job, options); } + public async removeJob(jobId: string) { + if (!this.jobQueue) { + throw new Error('Job queue not initialized.'); + } + + console.log(`Removing job ${jobId}`); + const job = await this.jobQueue.getJob(jobId); + if (job) { + await job.remove(); + console.log(`Successfully removed job ${jobId}`); + } else { + console.log(`Job ${jobId} not found, nothing to remove`); + } + } + + public async getJob(jobId: string): Promise { + if (!this.jobQueue) { + throw new Error('Job queue not initialized.'); + } + + try { + return await this.jobQueue.getJob(jobId); + } catch (error) { + console.error(`Error getting job ${jobId}:`, error); + return null; + } + } + private handleJobJob = async (job: Job) => { const input = job.data as JobInput; const handler = jobHandlerMap[input.type] as JobHandler; diff --git a/apps/server/src/types/ai.ts b/apps/server/src/types/ai.ts index 98f80f9f..9667961e 100644 --- a/apps/server/src/types/ai.ts +++ b/apps/server/src/types/ai.ts @@ -8,6 +8,7 @@ export const assistantWorkflowInputSchema = z.object({ userInput: z.string(), workspaceId: z.string(), userId: z.string(), + workspaceName: z.string(), userDetails: z.object({ name: z.string(), email: z.string(), @@ -54,7 +55,7 @@ export const searchResultsOutputSchema = z.object({ sourceId: z.string(), score: z.number(), type: z.string(), - metadata: z.record(z.any()), + metadata: z.array(z.record(z.string(), z.any())), }) ), searchType: z.enum(['semantic', 'keyword', 'database', 'hybrid']), @@ -68,7 +69,7 @@ export const rankedResultsOutputSchema = z.object({ sourceId: z.string(), relevanceScore: z.number(), type: z.string(), - metadata: z.record(z.any()), + metadata: z.array(z.record(z.string(), z.any())), }) ), citations: z.array( @@ -109,10 +110,29 @@ export const WorkflowConfig = { // TYPE EXPORTS // ============================================================================ -export type AssistantWorkflowInput = z.infer; -export type AssistantWorkflowOutput = z.infer; -export type IntentClassificationOutput = z.infer; +export type AssistantWorkflowInput = z.infer< + typeof assistantWorkflowInputSchema +>; +export type AssistantWorkflowOutput = z.infer< + typeof assistantWorkflowOutputSchema +>; +export type IntentClassificationOutput = z.infer< + typeof intentClassificationOutputSchema +>; export type QueryRewriteOutput = z.infer; export type SearchResultsOutput = z.infer; export type RankedResultsOutput = z.infer; -export type AnswerOutput = z.infer; \ No newline at end of file +export type AnswerOutput = z.infer; + +// ========================================================================= +// HYBRID SEARCH ARGS +// ========================================================================= + +export type HybridSearchArgs = { + semanticQuery: string; + keywordQuery: string; + workspaceId: string; + userId: string; + maxResults?: number; + selectedContextNodeIds?: string[]; +}; diff --git a/hosting/docker/docker-compose.yaml b/hosting/docker/docker-compose.yaml index 93c3e7a0..c4d70646 100644 --- a/hosting/docker/docker-compose.yaml +++ b/hosting/docker/docker-compose.yaml @@ -151,11 +151,11 @@ services: REDIS_URL: 'redis://:your_valkey_password@valkey:6379/0' REDIS_DB: '0' # Optional variables: - # REDIS_JOBS_QUEUE_NAME: 'jobs' - # REDIS_JOBS_QUEUE_PREFIX: 'colanode' - # REDIS_TUS_LOCK_PREFIX: 'colanode:tus:lock' - # REDIS_TUS_KV_PREFIX: 'colanode:tus:kv' - # REDIS_EVENTS_CHANNEL: 'events' + REDIS_JOBS_QUEUE_NAME: 'jobs' + REDIS_JOBS_QUEUE_PREFIX: 'colanode' + REDIS_TUS_LOCK_PREFIX: 'colanode:tus:lock' + REDIS_TUS_KV_PREFIX: 'colanode:tus:kv' + REDIS_EVENTS_CHANNEL: 'events' # ─────────────────────────────────────────────────────────────── # S3 configuration for files. diff --git a/package-lock.json b/package-lock.json index a5f3c5cb..18a3c63b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "@electron-forge/publisher-github": "^7.8.3", "@electron/fuses": "^2.0.0", "@electron/rebuild": "^4.0.1", + "@malept/cross-spawn-promise": "^2.0.0", "@tailwindcss/postcss": "^4.1.11", "@types/better-sqlite3": "^7.6.13", "@types/electron-squirrel-startup": "^1.0.2", @@ -86,19 +87,20 @@ "name": "@colanode/server", "version": "1.0.0", "dependencies": { + "@ai-sdk/google": "^2.0.6", + "@ai-sdk/openai": "^2.0.15", "@aws-sdk/client-s3": "^3.863.0", "@colanode/core": "*", "@colanode/crdt": "*", - "@fastify/cors": "^11.1.0", - "@fastify/websocket": "^11.2.0", - "@langchain/core": "^0.3.68", - "@langchain/google-genai": "^0.2.16", - "@langchain/langgraph": "^0.4.3", - "@langchain/openai": "^0.6.6", + "@fastify/cors": "^11.0.1", + "@fastify/websocket": "^11.1.0", "@node-rs/argon2": "^2.0.2", + "@opentelemetry/auto-instrumentations-node": "^0.62.1", + "@opentelemetry/sdk-node": "^0.203.0", "@redis/client": "^5.8.0", "@tus/s3-store": "^2.0.0", "@tus/server": "^2.3.0", + "ai": "^5.0.15", "bullmq": "^5.56.9", "diff": "^8.0.2", "dotenv": "^17.2.1", @@ -108,8 +110,7 @@ "js-sha256": "^0.11.0", "ky": "^1.8.2", "kysely": "^0.28.4", - "langchain": "^0.3.30", - "langfuse-langchain": "^3.38.4", + "langfuse-vercel": "^3.38.4", "ms": "^2.1.3", "nodemailer": "^7.0.5", "pg": "^8.16.3", @@ -118,6 +119,7 @@ "zod": "^4.0.15" }, "devDependencies": { + "@types/ms": "^2.1.0", "@types/node": "^24.2.0", "@types/nodemailer": "^6.4.17", "@types/pg": "^8.15.5", @@ -136,6 +138,15 @@ "node": ">=0.3.1" } }, + "apps/server/node_modules/zod": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", + "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "apps/web": { "name": "@colanode/web", "dependencies": { @@ -152,6 +163,84 @@ "vite-plugin-pwa": "^1.0.2" } }, + "node_modules/@ai-sdk/gateway": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.7.tgz", + "integrity": "sha512-Athrq7OARuNc0iHZJP6InhSQ53tImCc990vMWyR1UHaZgPZJbXjKhIMiOj54F0I0Nlemx48V4fHYUTfLkJotnQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, + "node_modules/@ai-sdk/google": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.6.tgz", + "integrity": "sha512-8acuseWJI+RRH99JDWM/n7IJRuuGNa4YzLXB/leqE/ZByHyIiVWGADjJi/vfnJnmdM5fQnezJ6SRTF6feI5rSQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.15.tgz", + "integrity": "sha512-/IUyQ9ck4uUTtGojvQamcUWpNWkwpL/P1F6LYRxpQGj07H00oJEBH/VUizrIq0ZvW/vkuK6c6X4UJS9PrdYyxA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", + "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.3.tgz", + "integrity": "sha512-kAxIw1nYmFW1g5TvE54ZB3eNtgZna0RnLjPUp1ltz1+t9xkXJIuDT4atrwfau9IbS0BOef38wqrI8CjFfQrxhw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.3", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -190,23 +279,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -5525,133 +5597,21 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@langchain/core": { - "version": "0.3.68", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.68.tgz", - "integrity": "sha512-dWPT1h9ObG1TK9uivFTk/pgBULZ6/tBmq8czGUjZjR+1xh9jB4tm/D5FY6o5FklXcEpnAI9peNq2x17Kl9wbMg==", - "license": "MIT", - "dependencies": { - "@cfworker/json-schema": "^4.0.2", - "ansi-styles": "^5.0.0", - "camelcase": "6", - "decamelize": "1.2.0", - "js-tiktoken": "^1.0.12", - "langsmith": "^0.3.46", - "mustache": "^4.2.0", - "p-queue": "^6.6.2", - "p-retry": "4", - "uuid": "^10.0.0", - "zod": "^3.25.32", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@langchain/core/node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "license": "MIT" - }, - "node_modules/@langchain/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@langchain/core/node_modules/langsmith": { - "version": "0.3.50", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.50.tgz", - "integrity": "sha512-yosW6sR0EFnMnYKKyBmcqTNknDVOs+dUfcswWk80JoRxox6WEyel7hmSkSzabP/GmTs0hXbrtc+lZwpJWSnI0w==", - "license": "MIT", - "dependencies": { - "@types/uuid": "^10.0.0", - "chalk": "^4.1.2", - "console-table-printer": "^2.12.1", - "p-queue": "^6.6.2", - "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "@opentelemetry/api": "*", - "@opentelemetry/exporter-trace-otlp-proto": "*", - "@opentelemetry/sdk-trace-base": "*", - "openai": "*" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@opentelemetry/exporter-trace-otlp-proto": { - "optional": true - }, - "@opentelemetry/sdk-trace-base": { - "optional": true - }, - "openai": { - "optional": true - } - } - }, - "node_modules/@langchain/core/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@langchain/core/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/colinhacks" + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" } }, - "node_modules/@langchain/core/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/@langchain/google-genai": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.2.16.tgz", - "integrity": "sha512-7xgQfZtn4EcKL7HYV7c5ZZUTjzjY0eh1Ex2570uwEBIUlyP5GFYeRe7gizx6965DYu6rktkXRMHaU0CuOXxAXQ==", - "license": "MIT", - "dependencies": { - "@google/generative-ai": "^0.24.0", - "uuid": "^11.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.58 <0.4.0" - } - }, - "node_modules/@langchain/google-genai/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, "funding": [ { "type": "individual", @@ -5670,44 +5630,11 @@ "node": ">= 12.13.0" } }, - "node_modules/@langchain/langgraph": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.4.3.tgz", - "integrity": "sha512-wYi0LsJ+VQMpOHradx0apZtDXR5iINLus9mtvaVwT2qS78wd9Ic/XWKHHpVUNGDpO2KalTkmJWWXVjYKW5C3Rw==", - "license": "MIT", - "dependencies": { - "@langchain/langgraph-checkpoint": "^0.1.0", - "@langchain/langgraph-sdk": "~0.0.105", - "uuid": "^10.0.0", - "zod": "^3.25.32" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.58 < 0.4.0", - "zod-to-json-schema": "^3.x" - }, - "peerDependenciesMeta": { - "zod-to-json-schema": { - "optional": true - } - } - }, - "node_modules/@langchain/langgraph-checkpoint": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.1.0.tgz", - "integrity": "sha512-7oY5b0VQSxcV3DgoHdXiCgBhEzml/ZjZfKNeuq6oZ3ggcdUICa/fzrIBbFju6gxPU8ly93s0OsjF4yURnHw70Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, "node_modules/@mastra/core/node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "extraneous": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5716,158 +5643,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/@langchain/langgraph-checkpoint/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@langchain/langgraph-sdk": { - "version": "0.0.105", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.105.tgz", - "integrity": "sha512-3DD1W1wnbP48807qq+5gY248mFcwwNGqKdmZt05P3zeLpfP5Sfm6ELzVvqHGpr+qumP0yGRZs/7qArYGXRRfcQ==", - "license": "MIT", - "dependencies": { - "@upstash/redis": "^1.35.1", - "ai": "^4.3.16", - "async-mutex": "^0.5.0", - "js-tiktoken": "^1.0.20", - "json-schema": "^0.4.0", - "pg": "^8.16.3", - "pg-pool": "^3.10.1", - "postgres": "^3.4.7", - "redis": "^5.6.0", - "xxhash-wasm": "^1.1.0", - "zod": "^3.25.67", - "zod-to-json-schema": "^3.24.5" - }, - "peerDependencies": { - "@langchain/core": ">=0.2.31 <0.4.0", - "react": "^18 || ^19", - "react-dom": "^18 || ^19" - }, - "peerDependenciesMeta": { - "@langchain/core": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/@langchain/langgraph/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@langchain/langgraph/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@langchain/openai": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.6.6.tgz", - "integrity": "sha512-0fxSg290WTCTEM0PECDGfst2QYUiULKhzyydaOPLMQ5pvWHjJkzBudx+CyHkeQ8DvGXysJteSmZzAMjRCj4duQ==", - "license": "MIT", - "dependencies": { - "js-tiktoken": "^1.0.12", - "openai": "^5.12.1", - "zod": "^3.25.32" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.68 <0.4.0" - } - }, - "node_modules/@langchain/openai/node_modules/openai": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-5.12.1.tgz", - "integrity": "sha512-26s536j4Fi7P3iUma1S9H33WRrw0Qu8pJ2nYJHffrlKHPU0JK4d0r3NcMgqEcAeTdNLGYNyoFsqN4g4YE9vutg==", - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@langchain/openai/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@mastra/rag": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@mastra/rag/-/rag-1.0.6.tgz", - "integrity": "sha512-5qas0F/4LThMiSqAMRbGxsCNZPVrXLacbxhFYskINwmBMOGg2EaTxB/hAI1lv5/6o/BOQhEU/rl8UDRZkST1yg==", - "license": "Apache-2.0", - "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "big.js": "^7.0.1", - "js-tiktoken": "^1.0.20", - "node-html-better-parser": "^1.5.2", - "pathe": "^2.0.3", - "zeroentropy": "0.1.0-alpha.6", - "zod": "^3.25.67" - }, - "peerDependencies": { - "@mastra/core": ">=0.10.0-0 <0.13.0-0", - "ai": "^4.0.0" - } - }, - "node_modules/@mastra/schema-compat": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/@mastra/schema-compat/-/schema-compat-0.10.5.tgz", - "integrity": "sha512-Qhz8W4Hz7b9tNoVW306NMzotVy11bya1OjiTN+pthj00HZoaH7nmK8SWBiX4drS+PyhIqA16X5AenELe2QgZag==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0", - "zod-from-json-schema": "^0.0.5", - "zod-to-json-schema": "^3.24.5" - }, - "peerDependencies": { - "ai": "^4.0.0", - "zod": "^3.0.0" - } - }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -6633,20 +6408,20 @@ } }, "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.62.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.62.0.tgz", - "integrity": "sha512-h5g+VNJjiyX6u/IQpn36ZCHOENg1QW0GgBOHBcFGnHBBhmTww4R3brExdeuYbvLj3UQY09n+UHFEoMOqkhq07A==", + "version": "0.62.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.62.1.tgz", + "integrity": "sha512-FmPlWS7Dg6E3kP0vv19Pyhq3sqSi8tyn8IZh2RV73UsrcEZeQ3gUTf2Ar8iPRgbsxTukQHRoMGcaCVBsFVRVPw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/instrumentation-amqplib": "^0.50.0", "@opentelemetry/instrumentation-aws-lambda": "^0.54.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.56.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.57.0", "@opentelemetry/instrumentation-bunyan": "^0.49.0", "@opentelemetry/instrumentation-cassandra-driver": "^0.49.0", "@opentelemetry/instrumentation-connect": "^0.47.0", - "@opentelemetry/instrumentation-cucumber": "^0.18.0", - "@opentelemetry/instrumentation-dataloader": "^0.21.0", + "@opentelemetry/instrumentation-cucumber": "^0.18.1", + "@opentelemetry/instrumentation-dataloader": "^0.21.1", "@opentelemetry/instrumentation-dns": "^0.47.0", "@opentelemetry/instrumentation-express": "^0.52.0", "@opentelemetry/instrumentation-fastify": "^0.48.0", @@ -6657,7 +6432,7 @@ "@opentelemetry/instrumentation-hapi": "^0.50.0", "@opentelemetry/instrumentation-http": "^0.203.0", "@opentelemetry/instrumentation-ioredis": "^0.51.0", - "@opentelemetry/instrumentation-kafkajs": "^0.12.0", + "@opentelemetry/instrumentation-kafkajs": "^0.13.0", "@opentelemetry/instrumentation-knex": "^0.48.0", "@opentelemetry/instrumentation-koa": "^0.51.0", "@opentelemetry/instrumentation-lru-memoizer": "^0.48.0", @@ -6665,20 +6440,20 @@ "@opentelemetry/instrumentation-mongodb": "^0.56.0", "@opentelemetry/instrumentation-mongoose": "^0.50.0", "@opentelemetry/instrumentation-mysql": "^0.49.0", - "@opentelemetry/instrumentation-mysql2": "^0.49.0", + "@opentelemetry/instrumentation-mysql2": "^0.50.0", "@opentelemetry/instrumentation-nestjs-core": "^0.49.0", "@opentelemetry/instrumentation-net": "^0.47.0", "@opentelemetry/instrumentation-oracledb": "^0.29.0", - "@opentelemetry/instrumentation-pg": "^0.55.0", + "@opentelemetry/instrumentation-pg": "^0.56.0", "@opentelemetry/instrumentation-pino": "^0.50.0", "@opentelemetry/instrumentation-redis": "^0.51.0", "@opentelemetry/instrumentation-restify": "^0.49.0", "@opentelemetry/instrumentation-router": "^0.48.0", - "@opentelemetry/instrumentation-runtime-node": "^0.17.0", + "@opentelemetry/instrumentation-runtime-node": "^0.17.1", "@opentelemetry/instrumentation-socket.io": "^0.50.0", "@opentelemetry/instrumentation-tedious": "^0.22.0", "@opentelemetry/instrumentation-undici": "^0.14.0", - "@opentelemetry/instrumentation-winston": "^0.48.0", + "@opentelemetry/instrumentation-winston": "^0.48.1", "@opentelemetry/resource-detector-alibaba-cloud": "^0.31.3", "@opentelemetry/resource-detector-aws": "^2.3.0", "@opentelemetry/resource-detector-azure": "^0.10.0", @@ -6989,9 +6764,9 @@ } }, "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.56.0.tgz", - "integrity": "sha512-Jl2B/FYEb6tBCk9G31CMomKPikGU2g+CEhrGddDI0o1YeNpg3kAO9dExF+w489/IJUGZX6/wudyNvV7z4k9NjQ==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.57.0.tgz", + "integrity": "sha512-RfbyjaeZzX3mPhuaRHlSAQyfX3skfeWOl30jrqSXtE9k0DPdnIqpHhdYS0C/DEDuZbwTmruVJ4cUwMBw5Z6FAg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", @@ -7058,9 +6833,9 @@ } }, "node_modules/@opentelemetry/instrumentation-cucumber": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.18.0.tgz", - "integrity": "sha512-i+cUbLHvRShuevtM0NwjQR9wnABhmYw8+dbgD57LNBde7xkuSDot0CTzX+pYn32djtQ1bPYZiLf+uwS0JsMUrw==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.18.1.tgz", + "integrity": "sha512-gTfT7AuA0UH0TvqWOXnyr2KCv7mvZsOUmqCrtnU/RDcZ9J3nIX4OBfl7VVXE0fJlLqP7KIDggQ8O9g7rmaVLhA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.203.0", @@ -7074,9 +6849,9 @@ } }, "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.21.0.tgz", - "integrity": "sha512-Xu4CZ1bfhdkV3G6iVHFgKTgHx8GbKSqrTU01kcIJRGHpowVnyOPEv1CW5ow+9GU2X4Eki8zoNuVUenFc3RluxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.21.1.tgz", + "integrity": "sha512-hNAm/bwGawLM8VDjKR0ZUDJ/D/qKR3s6lA5NV+btNaPVm2acqhPcT47l2uCVi+70lng2mywfQncor9v8/ykuyw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.203.0" @@ -7252,9 +7027,9 @@ } }, "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.12.0.tgz", - "integrity": "sha512-bIe4aSAAxytp88nzBstgr6M7ZiEpW6/D1/SuKXdxxuprf18taVvFL2H5BDNGZ7A14K27haHqzYqtCTqFXHZOYg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.13.0.tgz", + "integrity": "sha512-FPQyJsREOaGH64hcxlzTsIEQC4DYANgTwHjiB7z9lldmvua1LRMVn3/FfBlzXoqF179B0VGYviz6rn75E9wsDw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.203.0", @@ -7383,9 +7158,9 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.49.0.tgz", - "integrity": "sha512-dCub9wc02mkJWNyHdVEZ7dvRzy295SmNJa+LrAJY2a/+tIiVBQqEAajFzKwp9zegVVnel9L+WORu34rGLQDzxA==", + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.50.0.tgz", + "integrity": "sha512-PoOMpmq73rOIE3nlTNLf3B1SyNYGsp7QXHYKmeTZZnJ2Ou7/fdURuOhWOI0e6QZ5gSem18IR1sJi6GOULBQJ9g==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.203.0", @@ -7449,14 +7224,14 @@ } }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.55.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.55.0.tgz", - "integrity": "sha512-yfJ5bYE7CnkW/uNsnrwouG/FR7nmg09zdk2MSs7k0ZOMkDDAE3WBGpVFFApGgNu2U+gtzLgEzOQG4I/X+60hXw==", + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.56.0.tgz", + "integrity": "sha512-A/J4SlGX8Y0Wwp7Y66fsNCFT/1h9lmBzqwTnfWW/bULtcKFqkQfqhs3G8+4cRxX02UI2z7T1aW5bsyc6QSYc1Q==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/sql-common": "^0.41.0", "@types/pg": "8.15.4", "@types/pg-pool": "2.0.6" @@ -7468,6 +7243,17 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@types/pg": { + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", + "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@opentelemetry/instrumentation-pino": { "version": "0.50.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.50.0.tgz", @@ -7536,9 +7322,9 @@ } }, "node_modules/@opentelemetry/instrumentation-runtime-node": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.17.0.tgz", - "integrity": "sha512-O+xc0woqrSjue5IgpCCMvlgsuDrq6DDEfiHW3S3vRMCjXE1ZoPjaDE/K6EURorN+tjnzZQN1gOMSrscSGAbjHg==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.17.1.tgz", + "integrity": "sha512-c1FlAk+bB2uF9a8YneGmNPTl7c/xVaan4mmWvbkWcOmH/ipKqR1LaKUlz/BMzLrJLjho1EJlG2NrS2w2Arg+nw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.203.0" @@ -7600,9 +7386,9 @@ } }, "node_modules/@opentelemetry/instrumentation-winston": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.48.0.tgz", - "integrity": "sha512-QuKbswAaQfRULhtlYbeNC9gOAXPxOSCE4BjIzuY1oEsc84kIsHUjn3yvY9Q83s3eg3j0JycNcAMi8u0yTl5PIQ==", + "version": "0.48.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.48.1.tgz", + "integrity": "sha512-XyOuVwdziirHHYlsw+BWrvdI/ymjwnexupKA787zQQ+D5upaE/tseZxjfQa7+t4+FdVLxHICaMTmkSD4yZHpzQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api-logs": "^0.203.0", @@ -7950,15 +7736,6 @@ "@opentelemetry/api": "^1.1.0" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -7970,6 +7747,70 @@ "node": ">=14" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -9577,61 +9418,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sindresorhus/slugify": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", - "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", - "license": "MIT", - "dependencies": { - "@sindresorhus/transliterate": "^1.0.0", - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sindresorhus/slugify/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sindresorhus/transliterate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", - "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@smithy/abort-controller": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.5.tgz", @@ -10363,6 +10149,12 @@ "sqlite-wasm": "bin/index.js" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", @@ -10802,16 +10594,16 @@ } }, "node_modules/@tiptap/core": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.0.9.tgz", - "integrity": "sha512-1zdDyILerBcD3P0fu8kCtPLOFj0R5utjexCQ2CZ46pckn/Wk4V+WUBARzhG5Yz2JDkmJIUIcmLBVrL6G1rjJWg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.2.0.tgz", + "integrity": "sha512-1Dk1AIwzJejcyi4FOEcKoBSLMkHMfxeDiQ0daG51eS1IZ1ZSF+Xlhg9OD5sAjbUTGQjK1HfU6kGwS9wJcR/9ZQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^3.0.9" + "@tiptap/pm": "^3.2.0" } }, "node_modules/@tiptap/extension-blockquote": { @@ -10872,9 +10664,9 @@ } }, "node_modules/@tiptap/extension-code-block": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.0.9.tgz", - "integrity": "sha512-H692k9sHIE3rR3S+BIknQXsLb8HSojk+7gQ5DV0hYajSzpJ02OUL4AnNlpMuSgZuaq+ljpN4sT8kCIzIE1kQxw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.2.0.tgz", + "integrity": "sha512-rGkwir451oAegeTMZw0h0JwfLfbexrgXDWw5eUpQ32/5M3n07HU2+EamTSKt7VW76AxdlehEfsd7yo+FkuVlWA==", "license": "MIT", "peer": true, "funding": { @@ -10882,23 +10674,23 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.0.9", - "@tiptap/pm": "^3.0.9" + "@tiptap/core": "^3.2.0", + "@tiptap/pm": "^3.2.0" } }, "node_modules/@tiptap/extension-code-block-lowlight": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.0.9.tgz", - "integrity": "sha512-J5REgsah4yCaiWwy6FOygbv5FlHw28xzqxdIqm3922uq+l2LKwCAF4EwR3u19ZLGgtH2Wy27BClR97JZPLvVCQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.2.0.tgz", + "integrity": "sha512-DZhxaxWwWuuhSemTp4ad0pBKUIQYJzqld/3hHOz/RBKfVN72dFAJc7ElKa206T4Bz6KH/W04fB3oc1nIv7cOyw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.0.9", - "@tiptap/extension-code-block": "^3.0.9", - "@tiptap/pm": "^3.0.9", + "@tiptap/core": "^3.2.0", + "@tiptap/extension-code-block": "^3.2.0", + "@tiptap/pm": "^3.2.0", "highlight.js": "^11", "lowlight": "^2 || ^3" } @@ -11084,9 +10876,9 @@ } }, "node_modules/@tiptap/pm": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.0.9.tgz", - "integrity": "sha512-cJdnpGyirRxwi6M4IkyapEK/jhcjFXdfX3uhJp/4uVH1dynNXalV0gE/YnH/yt55kzwvG9OUrwOQt+t1iXgNog==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.2.0.tgz", + "integrity": "sha512-2UTlVts1Q7snQaUlxUtXfwEndNgcBcEIf74f1aEzcLzJRwBRRexCS6M0n+F50IifoB0OVBZENMBv1X/YXIPGPA==", "license": "MIT", "dependencies": { "prosemirror-changeset": "^2.3.0", @@ -11447,12 +11239,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/diff-match-patch": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", - "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", - "license": "MIT" - }, "node_modules/@types/electron-squirrel-startup": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/electron-squirrel-startup/-/electron-squirrel-startup-1.0.2.tgz", @@ -11485,6 +11271,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -11499,12 +11294,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -11590,7 +11379,6 @@ "version": "24.2.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.10.0" @@ -11619,7 +11407,6 @@ "version": "8.15.5", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -11714,6 +11501,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -12000,15 +11793,6 @@ "dev": true, "license": "ISC" }, - "node_modules/@upstash/redis": { - "version": "1.35.3", - "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.35.3.tgz", - "integrity": "sha512-hSjv66NOuahW3MisRGlSgoszU2uONAY2l5Qo3Sae8OT3/Tng9K+2/cBRuyPBX8egwEGcNNCF9+r0V6grNnhL+w==", - "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" - } - }, "node_modules/@vitejs/plugin-react": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.0.tgz", @@ -12227,7 +12011,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -12237,6 +12020,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, "license": "MIT", "dependencies": { "humanize-ms": "^1.2.1" @@ -12260,29 +12044,21 @@ } }, "node_modules/ai": { - "version": "4.3.19", - "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz", - "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==", + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.15.tgz", + "integrity": "sha512-EX5hF+NVFm6R11mvdZRbg6eJEjyMlniI4/xOnyTh4VtDQ457lhIgi3kDGrHW3/qw9ELon9m2e7AK3g5z5sLwsQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "1.1.3", - "@ai-sdk/provider-utils": "2.2.8", - "@ai-sdk/react": "1.2.12", - "@ai-sdk/ui-utils": "1.2.11", - "@opentelemetry/api": "1.9.0", - "jsondiffpatch": "0.6.0" + "@ai-sdk/gateway": "1.0.7", + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.3", + "@opentelemetry/api": "1.9.0" }, "engines": { "node": ">=18" }, "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } + "zod": "^3.25.76 || ^4" } }, "node_modules/ajv": { @@ -12630,15 +12406,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/assert-options": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/assert-options/-/assert-options-0.8.3.tgz", - "integrity": "sha512-s6v4HnA+vYSGO4eZX+F+I3gvF74wPk+m6Z1Q3w1Dsg4Pnv/R24vhKAasoMVZGvDpOOfTg1Qz4ptZnEbuy95XsQ==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -12673,21 +12440,6 @@ "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", "license": "MIT" }, - "node_modules/async-mutex": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", - "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -12853,19 +12605,6 @@ "node": "20.x || 22.x || 23.x || 24.x" } }, - "node_modules/big.js": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-7.0.1.tgz", - "integrity": "sha512-iFgV784tD8kq4ccF1xtNMZnXeZzVuXWWM+ERFzKQjv+A5G9HC8CY3DuV45vgzFFcW+u2tIvmF95+AzWgs6BjCg==", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bigjs" - } - }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -13271,6 +13010,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13718,6 +13458,7 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, "license": "MIT" }, "node_modules/colorspace": { @@ -13823,15 +13564,6 @@ "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/console-table-printer": { - "version": "2.14.6", - "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.6.tgz", - "integrity": "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==", - "license": "MIT", - "dependencies": { - "simple-wcswidth": "^1.0.1" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -14024,7 +13756,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/custom-error-instance": { @@ -14103,15 +13835,6 @@ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", "license": "MIT" }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -14261,15 +13984,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -14318,6 +14032,19 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -14328,12 +14055,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-match-patch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", - "license": "Apache-2.0" - }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -14486,6 +14207,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -15152,7 +14874,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -15301,6 +15022,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15310,6 +15032,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15354,6 +15077,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -15366,6 +15090,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -15929,11 +15654,14 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" + "node_modules/eventsource-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", + "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } }, "node_modules/execa": { "version": "1.0.0", @@ -16060,6 +15788,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -16097,12 +15831,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", - "license": "MIT" - }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -16223,12 +15951,6 @@ "node": ">=6" } }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, "node_modules/fast-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", @@ -16578,6 +16300,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, "node_modules/fractional-indexing-jittered": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fractional-indexing-jittered/-/fractional-indexing-jittered-1.0.0.tgz", @@ -16757,19 +16485,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gaxios/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/gcp-metadata": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", @@ -16854,6 +16569,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -16927,6 +16643,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -17132,6 +16849,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -17256,6 +16974,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -17268,6 +16987,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -17291,11 +17011,14 @@ "node": ">= 0.4" } }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", @@ -17306,81 +17029,6 @@ "react-is": "^16.7.0" } }, - "node_modules/hono": { - "version": "4.8.12", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.8.12.tgz", - "integrity": "sha512-MQSKk1Mg7b74k8l+A025LfysnLtXDKkE4pLaSsYRQC5iy85lgZnuyeQ1Wynair9mmECzoLu+FtJtqNZSoogBDQ==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/hono-openapi": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/hono-openapi/-/hono-openapi-0.4.8.tgz", - "integrity": "sha512-LYr5xdtD49M7hEAduV1PftOMzuT8ZNvkyWfh1DThkLsIr4RkvDb12UxgIiFbwrJB6FLtFXLoOZL9x4IeDk2+VA==", - "license": "MIT", - "dependencies": { - "json-schema-walker": "^2.0.0" - }, - "peerDependencies": { - "@hono/arktype-validator": "^2.0.0", - "@hono/effect-validator": "^1.2.0", - "@hono/typebox-validator": "^0.2.0 || ^0.3.0", - "@hono/valibot-validator": "^0.5.1", - "@hono/zod-validator": "^0.4.1", - "@sinclair/typebox": "^0.34.9", - "@valibot/to-json-schema": "^1.0.0-beta.3", - "arktype": "^2.0.0", - "effect": "^3.11.3", - "hono": "^4.6.13", - "openapi-types": "^12.1.3", - "valibot": "^1.0.0-beta.9", - "zod": "^3.23.8", - "zod-openapi": "^4.0.0" - }, - "peerDependenciesMeta": { - "@hono/arktype-validator": { - "optional": true - }, - "@hono/effect-validator": { - "optional": true - }, - "@hono/typebox-validator": { - "optional": true - }, - "@hono/valibot-validator": { - "optional": true - }, - "@hono/zod-validator": { - "optional": true - }, - "@sinclair/typebox": { - "optional": true - }, - "@valibot/to-json-schema": { - "optional": true - }, - "arktype": { - "optional": true - }, - "effect": { - "optional": true - }, - "hono": { - "optional": true - }, - "valibot": { - "optional": true - }, - "zod": { - "optional": true - }, - "zod-openapi": { - "optional": true - } - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -17388,22 +17036,6 @@ "dev": true, "license": "ISC" }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -17456,6 +17088,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.0.0" @@ -17465,7 +17098,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -18381,6 +18013,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -18398,15 +18031,6 @@ "integrity": "sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==", "license": "MIT" }, - "node_modules/js-tiktoken": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.20.tgz", - "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.5.1" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18487,15 +18111,6 @@ "dequal": "^2.0.3" } }, - "node_modules/json-schema-to-zod": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-2.6.1.tgz", - "integrity": "sha512-uiHmWH21h9FjKJkRBntfVGTLpYlCZ1n98D0izIlByqQLqpmkQpNTBtfbdP04Na6+43lgsvrShFh2uWLkQDKJuQ==", - "license": "ISC", - "bin": { - "json-schema-to-zod": "dist/cjs/cli.js" - } - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -18503,19 +18118,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-walker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/json-schema-walker/-/json-schema-walker-2.0.0.tgz", - "integrity": "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw==", - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.1.0", - "clone": "^2.1.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -18544,35 +18146,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsondiffpatch": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", - "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", - "license": "MIT", - "dependencies": { - "@types/diff-match-patch": "^1.0.36", - "chalk": "^5.3.0", - "diff-match-patch": "^1.0.5" - }, - "bin": { - "jsondiffpatch": "bin/jsondiffpatch.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/jsondiffpatch/node_modules/chalk": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", - "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -18659,164 +18232,6 @@ "node": ">=20.0.0" } }, - "node_modules/langchain": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.30.tgz", - "integrity": "sha512-UyVsfwHDpHbrnWrjWuhJHqi8Non+Zcsf2kdpDTqyJF8NXrHBOpjdHT5LvPuW9fnE7miDTWf5mLcrWAGZgcrznQ==", - "license": "MIT", - "dependencies": { - "@langchain/openai": ">=0.1.0 <0.7.0", - "@langchain/textsplitters": ">=0.0.0 <0.2.0", - "js-tiktoken": "^1.0.12", - "js-yaml": "^4.1.0", - "jsonpointer": "^5.0.1", - "langsmith": "^0.3.33", - "openapi-types": "^12.1.3", - "p-retry": "4", - "uuid": "^10.0.0", - "yaml": "^2.2.1", - "zod": "^3.25.32" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/anthropic": "*", - "@langchain/aws": "*", - "@langchain/cerebras": "*", - "@langchain/cohere": "*", - "@langchain/core": ">=0.3.58 <0.4.0", - "@langchain/deepseek": "*", - "@langchain/google-genai": "*", - "@langchain/google-vertexai": "*", - "@langchain/google-vertexai-web": "*", - "@langchain/groq": "*", - "@langchain/mistralai": "*", - "@langchain/ollama": "*", - "@langchain/xai": "*", - "axios": "*", - "cheerio": "*", - "handlebars": "^4.7.8", - "peggy": "^3.0.2", - "typeorm": "*" - }, - "peerDependenciesMeta": { - "@langchain/anthropic": { - "optional": true - }, - "@langchain/aws": { - "optional": true - }, - "@langchain/cerebras": { - "optional": true - }, - "@langchain/cohere": { - "optional": true - }, - "@langchain/deepseek": { - "optional": true - }, - "@langchain/google-genai": { - "optional": true - }, - "@langchain/google-vertexai": { - "optional": true - }, - "@langchain/google-vertexai-web": { - "optional": true - }, - "@langchain/groq": { - "optional": true - }, - "@langchain/mistralai": { - "optional": true - }, - "@langchain/ollama": { - "optional": true - }, - "@langchain/xai": { - "optional": true - }, - "axios": { - "optional": true - }, - "cheerio": { - "optional": true - }, - "handlebars": { - "optional": true - }, - "peggy": { - "optional": true - }, - "typeorm": { - "optional": true - } - } - }, - "node_modules/langchain/node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "license": "MIT" - }, - "node_modules/langchain/node_modules/langsmith": { - "version": "0.3.50", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.50.tgz", - "integrity": "sha512-yosW6sR0EFnMnYKKyBmcqTNknDVOs+dUfcswWk80JoRxox6WEyel7hmSkSzabP/GmTs0hXbrtc+lZwpJWSnI0w==", - "license": "MIT", - "dependencies": { - "@types/uuid": "^10.0.0", - "chalk": "^4.1.2", - "console-table-printer": "^2.12.1", - "p-queue": "^6.6.2", - "p-retry": "4", - "semver": "^7.6.3", - "uuid": "^10.0.0" - }, - "peerDependencies": { - "@opentelemetry/api": "*", - "@opentelemetry/exporter-trace-otlp-proto": "*", - "@opentelemetry/sdk-trace-base": "*", - "openai": "*" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@opentelemetry/exporter-trace-otlp-proto": { - "optional": true - }, - "@opentelemetry/sdk-trace-base": { - "optional": true - }, - "openai": { - "optional": true - } - } - }, - "node_modules/langchain/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/langchain/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/langfuse": { "version": "3.38.4", "resolved": "https://registry.npmjs.org/langfuse/-/langfuse-3.38.4.tgz", @@ -18841,10 +18256,10 @@ "node": ">=18" } }, - "node_modules/langfuse-langchain": { + "node_modules/langfuse-vercel": { "version": "3.38.4", - "resolved": "https://registry.npmjs.org/langfuse-langchain/-/langfuse-langchain-3.38.4.tgz", - "integrity": "sha512-7HJqouMrVOP9MFdu33M4G4uBFyQAIh/DqGYALfs41xqm7t99eZxKcTvt4rYZy67iQAhd58TG3q8+9haGzuLbOA==", + "resolved": "https://registry.npmjs.org/langfuse-vercel/-/langfuse-vercel-3.38.4.tgz", + "integrity": "sha512-gyhQuim2b1qpI7yiGnww9qzqsdpD59XagD+QB3eGHIrX+1ao7vbcrwL14H0GlsLIUM10Nud9wtEbxiFZP1PB7g==", "license": "MIT", "dependencies": { "langfuse": "^3.38.4", @@ -18854,7 +18269,7 @@ "node": ">=18" }, "peerDependencies": { - "langchain": ">=0.0.157 <0.4.0" + "ai": ">=3.2.44" } }, "node_modules/leven": { @@ -18981,6 +18396,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19002,6 +18418,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19023,6 +18440,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19044,6 +18462,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19065,6 +18484,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19086,6 +18506,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19107,6 +18528,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19128,6 +18550,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19149,6 +18572,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19170,6 +18594,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19339,6 +18764,12 @@ "lodash._basetostring": "~4.12.0" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -19520,6 +18951,21 @@ "node": ">=8" } }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -19648,6 +19094,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -19708,6 +19155,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -19717,6 +19165,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -20047,6 +19496,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -20126,26 +19576,6 @@ "semver": "^7.3.5" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -20170,21 +19600,18 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, "license": "MIT" }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -20322,15 +19749,6 @@ "node": ">=18" } }, - "node_modules/node-html-better-parser": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/node-html-better-parser/-/node-html-better-parser-1.5.2.tgz", - "integrity": "sha512-ytjqwEgBQeNt//M19gukAzqvcSEn2EJPk+3svNs3f2lc+K50eZdokBSyAqlL9pMXK2Z3rMOLe18iZKrnEJKqtQ==", - "license": "MIT", - "dependencies": { - "html-entities": "^2.3.2" - } - }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -20690,7 +20108,8 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/optionator": { "version": "0.9.4", @@ -21020,6 +20439,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/pathval": { @@ -21103,15 +20523,6 @@ "node": ">=4.0.0" } }, - "node_modules/pg-minify": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.8.0.tgz", - "integrity": "sha512-jO/oJOununpx8DzKgvSsWm61P8JjwXlaxSlbbfTBo1nvSWoo/+I6qZYaSN96jm/KDwa5d+JMQwPGgcP6HXDRow==", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/pg-pool": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", @@ -21121,24 +20532,6 @@ "pg": ">=8.0" } }, - "node_modules/pg-promise": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-11.15.0.tgz", - "integrity": "sha512-EUXpXn90yPVPKxQH4qqUAEVcApd2tp/JdR3wG6LzBUgaXTUYqwmuXG4vFhhZTCctzhfzRA20EbORb9H4aAgUHA==", - "license": "MIT", - "dependencies": { - "assert-options": "0.8.3", - "pg": "8.16.3", - "pg-minify": "1.8.0", - "spex": "3.4.1" - }, - "engines": { - "node": ">=16.0" - }, - "peerDependencies": { - "pg-query-stream": "4.10.3" - } - }, "node_modules/pg-protocol": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", @@ -21230,42 +20623,6 @@ "split2": "^4.0.0" } }, - "node_modules/pino-pretty": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.1.tgz", - "integrity": "sha512-TNNEOg0eA0u+/WuqH0MH0Xui7uqVk9D74ESOpjtebSQYbNWJk/dIxCXIxFsNfeN53JmtWqYHP2OrIZjT/CBEnA==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.2", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^4.0.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^5.0.2" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-pretty/node_modules/strip-json-comments": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.2.tgz", - "integrity": "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pino-std-serializers": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", @@ -21391,19 +20748,6 @@ } } }, - "node_modules/postgres": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", - "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", - "license": "Unlicense", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/porsager" - } - }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -21857,6 +21201,30 @@ "prosemirror-transform": "^1.1.0" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -21939,15 +21307,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/radash": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/radash/-/radash-12.1.1.tgz", - "integrity": "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==", - "license": "MIT", - "engines": { - "node": ">=14.18.0" - } - }, "node_modules/random-path": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/random-path/-/random-path-0.1.2.tgz", @@ -22559,6 +21918,20 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -22905,7 +22278,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT", "optional": true }, @@ -23188,12 +22560,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sift": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", - "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", - "license": "MIT" - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -23281,12 +22647,6 @@ "node": ">=10" } }, - "node_modules/simple-wcswidth": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", - "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", - "license": "MIT" - }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -23471,15 +22831,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/spex": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/spex/-/spex-3.4.1.tgz", - "integrity": "sha512-Br0Mu3S+c70kr4keXF+6K4B8ohR+aJjI9s7SbdsI3hliE1Riz4z+FQk7FQL+r7X1t90KPkpuKwQyITpCIQN9mg==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -24080,19 +23431,6 @@ "node": ">= 10" } }, - "node_modules/swr": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", - "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -24411,18 +23749,6 @@ "real-require": "^0.2.0" } }, - "node_modules/throttleit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", - "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tiny-each-async": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz", @@ -25225,12 +24551,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uncrypto": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", - "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", - "license": "MIT" - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -25242,7 +24562,6 @@ "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "devOptional": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -25820,15 +25139,6 @@ "defaults": "^1.0.3" } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -26575,16 +25885,6 @@ "node": ">=0.6.0" } }, - "node_modules/xstate": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.20.1.tgz", - "integrity": "sha512-i9ZpNnm/XhCOMUxae1suT8PjYNTStZWbhmuKt4xeTPaYG5TS0Fz0i+Ka5yxoNPpaHW3VW6JIowrwFgSTZONxig==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/xstate" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -26708,45 +26008,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zeroentropy": { - "version": "0.1.0-alpha.6", - "resolved": "https://registry.npmjs.org/zeroentropy/-/zeroentropy-0.1.0-alpha.6.tgz", - "integrity": "sha512-8roJUFph+VgQ13ABYm5XmtlHvabMD5r1TA9tOKQA7MVc2h0y2JymtmoWJl4qaaHQtpFkqzJu4pNY7rLksMOWBg==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - } - }, - "node_modules/zeroentropy/node_modules/@types/node": { - "version": "18.19.121", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.121.tgz", - "integrity": "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/zeroentropy/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, "node_modules/zod": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.15.tgz", - "integrity": "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "packages/client": { "name": "@colanode/client", "version": "1.0.0", @@ -26777,6 +26057,15 @@ "zod": "^4.0.15" } }, + "packages/core/node_modules/zod": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", + "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "packages/crdt": { "name": "@colanode/crdt", "version": "1.0.0", @@ -26800,6 +26089,15 @@ "node": ">=0.3.1" } }, + "packages/crdt/node_modules/zod": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", + "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "packages/ui": { "name": "@colanode/ui", "version": "1.0.0", @@ -26849,6 +26147,7 @@ "cmdk": "^1.1.1", "date-fns": "^4.1.0", "is-hotkey": "^0.2.0", + "lowlight": "^3.3.0", "lucide-react": "^0.539.0", "re-resizable": "^6.11.2", "react": "^19.1.1", @@ -26874,6 +26173,15 @@ "tw-animate-css": "^1.3.6" } }, + "packages/ui/node_modules/zod": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", + "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "scripts": { "name": "@colanode/scripts", "version": "1.0.0", diff --git a/packages/client/src/handlers/mutations/messages/message-create.ts b/packages/client/src/handlers/mutations/messages/message-create.ts index 45cb10c7..8e580094 100644 --- a/packages/client/src/handlers/mutations/messages/message-create.ts +++ b/packages/client/src/handlers/mutations/messages/message-create.ts @@ -59,7 +59,7 @@ export class MessageCreateMutationHandler const messageAttributes: MessageAttributes = { type: 'message', - subtype: 'standard', + subtype: input.subtype ?? 'standard', parentId: input.parentId, content: blocks, referenceId: input.referenceId, diff --git a/packages/client/src/mutations/messages/message-create.ts b/packages/client/src/mutations/messages/message-create.ts index f3b356dc..cc0ad309 100644 --- a/packages/client/src/mutations/messages/message-create.ts +++ b/packages/client/src/mutations/messages/message-create.ts @@ -7,6 +7,7 @@ export type MessageCreateMutationInput = { parentId: string; content: JSONContent; referenceId?: string; + subtype?: 'standard' | 'question'; }; export type MessageCreateMutationOutput = { diff --git a/packages/ui/package.json b/packages/ui/package.json index 3a23a886..f12b5317 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -70,6 +70,7 @@ "cmdk": "^1.1.1", "date-fns": "^4.1.0", "is-hotkey": "^0.2.0", + "lowlight": "^3.3.0", "lucide-react": "^0.539.0", "re-resizable": "^6.11.2", "react": "^19.1.1", diff --git a/packages/ui/src/components/messages/message-create.tsx b/packages/ui/src/components/messages/message-create.tsx index 4d2a9fe0..74822423 100644 --- a/packages/ui/src/components/messages/message-create.tsx +++ b/packages/ui/src/components/messages/message-create.tsx @@ -41,6 +41,7 @@ export const MessageCreate = forwardRef((_, ref) => { const messageEditorRef = useRef(null); const [content, setContent] = useState(null); const [replyTo, setReplyTo] = useState(null); + const [askAI, setAskAI] = useState(false); const hasContent = content != null && editorHasContent(content); @@ -79,9 +80,11 @@ export const MessageCreate = forwardRef((_, ref) => { workspaceId: workspace.id, referenceId: replyTo?.id, rootId: conversation.rootId, + subtype: askAI ? 'question' : undefined, }, onSuccess: () => { setReplyTo(null); + setAskAI(false); if (messageEditorRef.current) { messageEditorRef.current.clear(); messageEditorRef.current.focus(); @@ -170,6 +173,21 @@ export const MessageCreate = forwardRef((_, ref) => { )}
+ {isPending ? ( ) : (