UNPKG

devcontext

Version:

DevContext is a cutting-edge Model Context Protocol (MCP) server designed to provide developers with continuous, project-centric context awareness.

1,153 lines (1,044 loc) 37.4 kB
/** * updateConversationContext.tool.js * * MCP tool implementation for updating an existing conversation context * This tool processes new messages and code changes, manages topic shifts, * and ensures context continuity throughout the conversation */ import { z } from "zod"; import { executeQuery } from "../db.js"; import * as ConversationIntelligence from "../logic/ConversationIntelligence.js"; import * as KnowledgeProcessor from "../logic/KnowledgeProcessor.js"; import * as TimelineManagerLogic from "../logic/TimelineManagerLogic.js"; import * as IntentPredictorLogic from "../logic/IntentPredictorLogic.js"; import * as ActiveContextManager from "../logic/ActiveContextManager.js"; import * as ConversationSegmenter from "../logic/ConversationSegmenter.js"; import * as ConversationPurposeDetector from "../logic/ConversationPurposeDetector.js"; import * as ContextCompressorLogic from "../logic/ContextCompressorLogic.js"; import { logMessage } from "../utils/logger.js"; import { v4 as uuidv4 } from "uuid"; import { updateConversationContextInputSchema, updateConversationContextOutputSchema, } from "../schemas/toolSchemas.js"; /** * Handler for update_conversation_context tool * * @param {object} input - Tool input parameters * @param {object} sdkContext - SDK context * @returns {Promise<object>} Tool output */ async function handler(input, sdkContext) { try { logMessage("INFO", `update_conversation_context tool started`, { conversationId: input.conversationId, messageCount: input.newMessages?.length || 0, codeChangeCount: input.codeChanges?.length || 0, }); // 1. Extract input parameters with defaults const { conversationId, newMessages = [], codeChanges = [], preserveContextOnTopicShift = true, contextIntegrationLevel = "balanced", trackIntentTransitions = true, tokenBudget = 4000, } = input; // Validate conversation ID is provided if (!conversationId) { const error = new Error("conversationId is required"); error.code = "MISSING_CONVERSATION_ID"; throw error; } logMessage("DEBUG", `Processing update with parameters`, { preserveContextOnTopicShift, contextIntegrationLevel, trackIntentTransitions, }); // 2. Initialize tracking variables for context transitions let topicShift = false; let intentTransition = false; let previousIntent = null; let currentIntent = null; let contextPreserved = true; let currentFocus = null; // 3. Get current context state before changes try { const previousContextState = await ActiveContextManager.getActiveContextState(); logMessage("DEBUG", `Retrieved previous context state`, { hasPreviousContext: !!previousContextState, }); if (trackIntentTransitions) { previousIntent = await ConversationPurposeDetector.getActivePurpose( conversationId ); logMessage("DEBUG", `Retrieved previous intent`, { previousIntent }); } } catch (err) { logMessage( "WARN", `Failed to retrieve previous context state, continuing with defaults`, { error: err.message, } ); // Continue with defaults already initialized } // 4. Process new messages if any if (newMessages.length > 0) { logMessage("INFO", `Processing ${newMessages.length} new messages`); try { const processedMessages = await processNewMessages( conversationId, newMessages, { trackIntentTransitions, } ); topicShift = processedMessages.topicShift; logMessage("DEBUG", `Message processing completed`, { topicShift: topicShift, }); if (trackIntentTransitions) { intentTransition = processedMessages.intentTransition; currentIntent = processedMessages.currentIntent; if (intentTransition) { logMessage("INFO", `Intent transition detected`, { from: previousIntent, to: currentIntent, }); } } } catch (err) { logMessage("ERROR", `Failed to process new messages`, { error: err.message, conversationId, }); // Continue with code changes processing despite message error } } // 5. Process code changes if any if (codeChanges.length > 0) { logMessage("INFO", `Processing ${codeChanges.length} code changes`); try { const processedChanges = await processCodeChanges( conversationId, codeChanges ); // Update tracking variables with results from code changes if (processedChanges.focusChanged) { logMessage("INFO", `Focus changed due to code changes`, { newFocus: processedChanges.newFocus, }); // Code changes can also affect focus and sometimes intent if (trackIntentTransitions && !intentTransition) { try { // Only update if we haven't already detected a transition from messages const intentResult = await IntentPredictorLogic.updateIntent({ conversationId, codeChanges, }); if (intentResult.intentChanged) { intentTransition = true; currentIntent = intentResult.newIntent; logMessage("INFO", `Intent changed due to code changes`, { newIntent: currentIntent, }); } } catch (intentErr) { logMessage("WARN", `Failed to update intent from code changes`, { error: intentErr.message, }); // Continue without updating intent } } } } catch (err) { logMessage("ERROR", `Failed to process code changes`, { error: err.message, conversationId, }); // Continue with context management despite code change error } } // 6. Manage context continuity based on topic shifts and transitions if (topicShift || intentTransition) { logMessage( "INFO", `Topic shift or intent transition detected, managing context continuity`, { topicShift, intentTransition, preserveContextOnTopicShift, } ); // Determine if and how to preserve context if (!preserveContextOnTopicShift) { try { // Clear previous context if preservation not requested await ActiveContextManager.clearActiveContext(); contextPreserved = false; logMessage("INFO", `Cleared previous context due to topic shift`); // Initialize fresh context for new topic/intent if (currentIntent) { try { const recentEvents = await TimelineManagerLogic.getRecentEventsForConversation( conversationId, 10 ); const focusResult = await IntentPredictorLogic.predictFocusArea( recentEvents, codeChanges ); if (focusResult) { await ActiveContextManager.setActiveFocus( focusResult.type, focusResult.identifier ); currentFocus = focusResult; logMessage("INFO", `Set new focus area based on intent`, { type: focusResult.type, identifier: focusResult.identifier, }); } } catch (focusErr) { logMessage("WARN", `Failed to set new focus area`, { error: focusErr.message, }); // Continue without setting focus } } } catch (clearErr) { logMessage("ERROR", `Failed to clear context`, { error: clearErr.message, }); // Continue with next steps despite error } } else { try { // Integrate previous and new context const previousContextState = (await ActiveContextManager.getActiveContextState()) || {}; const integratedContext = await _integrateContexts( previousContextState, { topicShift, intentTransition, previousIntent, currentIntent, codeChanges, }, contextIntegrationLevel ); await ActiveContextManager.updateActiveContext(integratedContext); contextPreserved = true; logMessage("INFO", `Integrated previous and new context`, { contextIntegrationLevel, }); } catch (integrateErr) { logMessage("ERROR", `Failed to integrate contexts`, { error: integrateErr.message, }); // Continue with next steps despite error } } } else { logMessage( "DEBUG", `No topic shift or intent transition detected, preserving context` ); } // 7. Get final focus and context state if (!currentFocus) { try { currentFocus = await ActiveContextManager.getActiveFocus(); logMessage("DEBUG", `Retrieved current focus`, { focus: currentFocus ? `${currentFocus.type}:${currentFocus.identifier}` : "none", }); } catch (focusErr) { logMessage("WARN", `Failed to get current focus`, { error: focusErr.message, }); // Continue without focus } } // 8. Generate context synthesis let contextSynthesis; try { contextSynthesis = await generateContextSynthesis( conversationId, currentIntent, topicShift || intentTransition ); logMessage("DEBUG", `Generated context synthesis`, { synthesisLength: contextSynthesis?.length || 0, }); } catch (synthesisErr) { logMessage("WARN", `Failed to generate context synthesis`, { error: synthesisErr.message, }); contextSynthesis = null; } // 9. Update timeline with context update event try { await TimelineManagerLogic.recordEvent( "context_updated", { newMessagesCount: newMessages.length, codeChangesCount: codeChanges.length, topicShift, intentTransition: intentTransition ? { from: previousIntent, to: currentIntent, } : null, contextPreserved, contextIntegrationLevel: contextPreserved ? contextIntegrationLevel : "none", }, [], // No specific entity IDs conversationId ); logMessage("DEBUG", `Recorded context update in timeline`); } catch (timelineErr) { logMessage("WARN", `Failed to record context update in timeline`, { error: timelineErr.message, }); // Non-critical error, continue } // 10. Return the tool response logMessage( "INFO", `update_conversation_context tool completed successfully` ); const responseData = { status: "success", message: `Conversation context updated for ${conversationId}`, updatedFocus: currentFocus ? { type: currentFocus.type, identifier: currentFocus.identifier, } : undefined, contextContinuity: { topicShift, intentTransition, contextPreserved, }, synthesis: contextSynthesis, }; return { content: [ { type: "text", text: JSON.stringify(responseData), }, ], }; } catch (error) { // Log detailed error information logMessage("ERROR", `Error in update_conversation_context tool`, { error: error.message, stack: error.stack, input: { conversationId: input.conversationId, messageCount: input.newMessages?.length || 0, codeChangeCount: input.codeChanges?.length || 0, }, }); // Return error response const errorResponse = { error: true, errorCode: error.code || "UPDATE_FAILED", errorDetails: error.message, }; return { content: [ { type: "text", text: JSON.stringify(errorResponse), }, ], }; } } /** * Process new messages and detect topic shifts or intent transitions * * @param {string} conversationId - Conversation ID * @param {Array} messages - New messages to process * @param {object} options - Processing options * @returns {Promise<object>} Processing results */ async function processNewMessages(conversationId, messages, options = {}) { try { logMessage( "DEBUG", `Processing ${messages.length} messages for conversation ${conversationId}` ); const result = { topicShift: false, intentTransition: false, currentIntent: null, }; // Get the current active purpose if tracking transitions let previousIntent = null; if (options.trackIntentTransitions) { try { previousIntent = await ConversationPurposeDetector.getActivePurpose( conversationId ); logMessage("DEBUG", "Retrieved previous intent", { previousIntent }); } catch (error) { logMessage("WARN", "Failed to retrieve previous intent", { error: error.message, }); } } // Process each message for (const message of messages) { try { let isTopicShift = false; let activeTopicId = null; // Only check for topic shifts on user messages if (message.role === "user") { // Check for topic shift logMessage("DEBUG", "Checking for topic shift with user message"); isTopicShift = await ConversationIntelligence.detectTopicShift( message.content, conversationId ); if (isTopicShift) { logMessage("INFO", `Topic shift detected`, { messageContent: message.content.substring(0, 50) + (message.content.length > 50 ? "..." : ""), }); result.topicShift = true; } } // Record the message first without a topic segment ID // We'll update this after creating a new topic if needed logMessage("DEBUG", `Recording message from ${message.role}`); const recordedMessageId = await ConversationIntelligence.recordMessage( message.content, message.role, conversationId, [], // relatedContextEntityIds null, // topicSegmentId - will be updated later if needed options.trackIntentTransitions && result.currentIntent ? result.currentIntent.purposeType : null ); logMessage("DEBUG", `Message recorded with ID: ${recordedMessageId}`); // Handle topic shift if detected if (message.role === "user" && isTopicShift) { // First, close any currently active topic segment try { const activeTopic = await ConversationSegmenter.getActiveTopicForConversation( conversationId ); if (activeTopic) { logMessage("INFO", "Closing active topic segment", { topicId: activeTopic.topic_id, }); await ConversationSegmenter.closeTopicSegment( activeTopic.topic_id, recordedMessageId ); } } catch (closeError) { logMessage("WARN", "Failed to close active topic segment", { error: closeError.message, }); } // Generate a topic name let topicName = ""; try { topicName = await ConversationSegmenter.generateTopicName([ recordedMessageId, ]); } catch (nameError) { topicName = `Topic from: ${message.content.substring(0, 30)}...`; logMessage( "WARN", "Failed to generate topic name, using fallback", { error: nameError.message, } ); } // Create a new topic segment with the recorded message ID try { logMessage("INFO", "Creating new topic segment"); const newTopicId = await ConversationSegmenter.createNewTopicSegment( conversationId, recordedMessageId, // Use the actual message ID { name: topicName, description: message.content, } ); logMessage("INFO", `Created new topic segment`, { topicId: newTopicId, }); // Update the message with the new topic ID // This requires a database update since we've already recorded the message try { const updateQuery = ` UPDATE conversation_history SET topic_segment_id = ? WHERE message_id = ? `; await executeQuery(updateQuery, [newTopicId, recordedMessageId]); logMessage("DEBUG", "Updated message with new topic ID", { messageId: recordedMessageId, topicId: newTopicId, }); // Use this topic ID for tracking activeTopicId = newTopicId; } catch (updateError) { logMessage("ERROR", "Failed to update message with topic ID", { error: updateError.message, }); } } catch (topicError) { logMessage("ERROR", "Failed to create new topic segment", { error: topicError.message, }); } } else if (message.role === "user" && !isTopicShift) { // If no topic shift, associate with current active topic if any try { const activeTopic = await ConversationSegmenter.getActiveTopicForConversation( conversationId ); if (activeTopic) { const updateQuery = ` UPDATE conversation_history SET topic_segment_id = ? WHERE message_id = ? `; await executeQuery(updateQuery, [ activeTopic.topic_id, recordedMessageId, ]); logMessage("DEBUG", "Updated message with existing topic ID", { messageId: recordedMessageId, topicId: activeTopic.topic_id, }); activeTopicId = activeTopic.topic_id; } } catch (error) { logMessage( "WARN", "Failed to associate message with active topic", { error: error.message, } ); } } // Detect conversation purpose for each user message if (message.role === "user" && options.trackIntentTransitions) { try { // Get recent conversation history for context const recentHistory = await ConversationIntelligence.getConversationHistory( conversationId, 10 ); // Detect purpose based on message and conversation history const purposeResult = await ConversationPurposeDetector.detectConversationPurpose( message.content, recentHistory ); if (purposeResult) { const newPurpose = purposeResult.purpose; const currentPurpose = previousIntent ? previousIntent.purposeType : null; // Check if purpose has changed if (newPurpose !== currentPurpose) { logMessage("INFO", "Conversation purpose change detected", { from: currentPurpose, to: newPurpose, }); // Track the purpose transition await ConversationPurposeDetector.trackPurposeTransition( conversationId, currentPurpose, newPurpose, recordedMessageId ); // Update result for the handler function result.intentTransition = true; result.currentIntent = { purposeType: newPurpose, confidence: purposeResult.confidence, }; // Update previous intent for next iteration previousIntent = { purposeType: newPurpose, confidence: purposeResult.confidence, }; } } } catch (purposeError) { logMessage("WARN", "Failed to detect conversation purpose", { error: purposeError.message, }); } } // Update intent with the new message if (options.trackIntentTransitions) { try { const intentUpdateResult = await IntentPredictorLogic.updateIntent({ conversationId, messages: [message], messageId: recordedMessageId, }); // Check if intent has been updated during processing if (intentUpdateResult.intentChanged && !result.intentTransition) { result.intentTransition = true; result.currentIntent = intentUpdateResult.newIntent; logMessage("INFO", "Intent updated based on message content", { intent: intentUpdateResult.newIntent, }); } } catch (intentError) { logMessage("WARN", "Failed to update intent", { error: intentError.message, }); } } } catch (msgError) { logMessage("ERROR", `Failed to process message`, { error: msgError.message, role: message.role, content: message.content?.substring(0, 50) + "...", }); } } return result; } catch (error) { logMessage("ERROR", `Error processing new messages`, { error: error.message, conversationId, }); throw error; // Re-throw to be caught by the main handler } } /** * Process code changes and update related context * * @param {string} conversationId - Conversation ID * @param {Array} codeChanges - Array of code changes * @returns {Promise<object>} Processing results */ async function processCodeChanges(conversationId, codeChanges) { try { logMessage( "DEBUG", `Processing ${codeChanges.length} code changes for conversation ${conversationId}` ); const result = { focusChanged: false, newFocus: null, }; // If there are no code changes, return early if (!codeChanges || !codeChanges.length) { return result; } // Process code changes in parallel using Promise.allSettled for better error handling // This will never reject, ensuring we always get results even if some changes fail const processingPromises = codeChanges.map((change) => { // Ensure filePath exists before processing if (!change || !change.filePath) { logMessage("WARN", "Received invalid code change object, skipping", { change: JSON.stringify(change).substring(0, 100) + "...", }); return Promise.resolve({ success: false, filePath: change?.filePath || "unknown", error: "Invalid code change: missing filePath", }); } return KnowledgeProcessor.processCodeChange(change) .then((result) => { if (result.success) { logMessage("DEBUG", `Processed code change for ${change.filePath}`); } else { logMessage("WARN", `Failed to process code change`, { error: result.error || "Unknown error", path: change.filePath, }); } return result; }) .catch((processErr) => { // Extra safety - should never reach here as processCodeChange now handles errors logMessage("WARN", `Unexpected error processing code change`, { error: processErr.message, path: change.filePath, }); return { success: false, filePath: change.filePath, error: processErr.message, }; }); }); // Wait for all code changes to be processed const processingResults = await Promise.allSettled(processingPromises); // Extract the actual results and handle any rejected promises (should be none) const processedResults = processingResults.map((promiseResult) => { if (promiseResult.status === "fulfilled") { return promiseResult.value; } else { // This shouldn't happen but handle it anyway logMessage("ERROR", "Promise rejected during code change processing", { reason: promiseResult.reason?.message || "Unknown error", }); return { success: false, error: promiseResult.reason?.message || "Unknown error", }; } }); // Log a summary of the results const successCount = processedResults.filter((r) => r.success).length; const failureCount = processedResults.filter((r) => !r.success).length; if (failureCount > 0) { logMessage( "WARN", `${failureCount} of ${codeChanges.length} code changes failed processing` ); } else { logMessage( "INFO", `Successfully processed all ${codeChanges.length} code changes` ); } // Calculate new focus area based on code changes try { if (successCount > 0) { const mostSignificantChange = codeChanges.reduce((prev, current) => { // Simple heuristic: more changed lines = more significant const prevChangedLines = prev.changedLines?.length || 0; const currentChangedLines = current.changedLines?.length || 0; return currentChangedLines > prevChangedLines ? current : prev; }, codeChanges[0]); // If we have a significant change, set it as the focus if (mostSignificantChange) { const newFocus = { focus_type: "file", identifier: mostSignificantChange.filePath, description: `File ${mostSignificantChange.filePath} was modified`, }; try { // Update focus area await FocusAreaManagerLogic.setFocusArea(newFocus); result.focusChanged = true; result.newFocus = newFocus; } catch (focusError) { logMessage("WARN", "Failed to update focus area", { error: focusError.message, }); // Continue without updating focus } } } } catch (focusError) { // Ignore focus calculation errors logMessage("WARN", "Error calculating focus area from code changes", { error: focusError.message, }); } return result; } catch (error) { logMessage("ERROR", `Failed to process code changes`, { error: error.message, conversationId, }); // Return a default result instead of throwing return { focusChanged: false, newFocus: null, error: error.message, }; } } /** * Integrates previous and new context states * * @param {Object} previousContextState - Previous context state * @param {Object} changes - Change indicators (topic shift, intent transition, etc.) * @param {string} integrationLevel - How aggressively to integrate contexts * @returns {Promise<Object>} Integrated context */ async function _integrateContexts( previousContextState, changes, integrationLevel ) { const { topicShift, intentTransition, previousIntent, currentIntent, codeChanges, } = changes; try { logMessage("INFO", `Integrating contexts with level: ${integrationLevel}`); // Start with a copy of the previous context const integratedContext = { ...previousContextState }; // Determine how much to preserve based on integration level switch (integrationLevel) { case "minimal": // For minimal integration, only keep core focus and clear most context if (topicShift) { // Clear most context but keep current focus const currentFocus = integratedContext.focus; integratedContext.recentContextItems = []; integratedContext.focus = currentFocus; } break; case "aggressive": // For aggressive integration, preserve all context even with transitions // Just update the intent/purpose information if (intentTransition) { integratedContext.currentIntent = currentIntent; } break; case "balanced": default: // For balanced integration, preserve relevant context if (topicShift) { // Reduce context items but keep those relevant to current focus const currentFocus = integratedContext.focus; // Keep items that are still relevant to current focus or code changes if (integratedContext.recentContextItems) { const changedFilePaths = codeChanges.map( (change) => change.filePath ); integratedContext.recentContextItems = integratedContext.recentContextItems.filter((item) => { // Keep items related to current focus if ( item.relatedTo && item.relatedTo.includes(currentFocus?.identifier) ) { return true; } // Keep items related to changed files if ( item.path && changedFilePaths.some((path) => item.path.includes(path)) ) { return true; } // Keep very recent items if ( item.timestamp && Date.now() - item.timestamp < 5 * 60 * 1000 ) { // 5 minutes return true; } return false; }); } } // Always update intent information if (intentTransition) { integratedContext.currentIntent = currentIntent; // If we have code changes, adjust priorities based on new intent if (codeChanges.length > 0 && integratedContext.recentContextItems) { // Re-prioritize based on new intent integratedContext.recentContextItems.forEach((item) => { if (item.contentType === "code" && currentIntent) { // Adjust priority based on relevance to new intent if ( currentIntent === "debugging" && item.path && item.path.includes("test") ) { item.priority = Math.min(item.priority + 0.2, 1.0); } else if ( currentIntent === "feature_planning" && item.path && item.path.includes("docs") ) { item.priority = Math.min(item.priority + 0.2, 1.0); } // Add more intent-specific priority adjustments as needed } }); // Sort by adjusted priority integratedContext.recentContextItems.sort( (a, b) => b.priority - a.priority ); } } break; } return integratedContext; } catch (error) { logMessage("ERROR", `Error integrating contexts`, { error: error.message, }); // Fall back to previous context in case of error return previousContextState; } } /** * Generates a synthesis of the current context * * @param {string} conversationId - Conversation ID * @param {string} currentIntent - Current conversation intent * @param {boolean} contextChanged - Whether context has significantly changed * @returns {Promise<Object>} Context synthesis */ async function generateContextSynthesis( conversationId, currentIntent, contextChanged ) { try { logMessage("INFO", `Generating context synthesis`); // Get active context information const activeContext = await ActiveContextManager.getActiveContextState(); const activeFocus = await ActiveContextManager.getActiveFocus(); // Get recent messages for context const recentMessages = await ConversationIntelligence.getRecentMessages( conversationId, 5 ); // Generate a summary appropriate to the current state let summaryText = "Current conversation context"; if (contextChanged) { // More detailed summary for changed context if (activeFocus) { summaryText = `The conversation is now focused on ${activeFocus.type} "${activeFocus.identifier}"`; if (currentIntent) { const intentStr = typeof currentIntent === "string" ? currentIntent.replace(/_/g, " ") : currentIntent; summaryText += ` with the purpose of ${intentStr}`; } } else if (currentIntent) { const intentStr = typeof currentIntent === "string" ? currentIntent.replace(/_/g, " ") : currentIntent; summaryText = `The conversation is focused on ${intentStr}`; } // Add recent message summary if available if (recentMessages.length > 0) { const messageContent = recentMessages .map((msg) => msg.content) .join(" "); const messageSummary = await ContextCompressorLogic.summarizeText( messageContent, { targetLength: 150 } ); summaryText += `. Recent discussion: ${messageSummary}`; } } else { // Simpler summary for continued context if (activeFocus) { summaryText = `Continuing focus on ${activeFocus.type} "${activeFocus.identifier}"`; if (currentIntent) { const intentStr = typeof currentIntent === "string" ? currentIntent.replace(/_/g, " ") : currentIntent; summaryText += ` with ${intentStr}`; } } else if (currentIntent) { const intentStr = typeof currentIntent === "string" ? currentIntent.replace(/_/g, " ") : currentIntent; summaryText = `Continuing with ${intentStr}`; } } // Identify top priorities based on current context const topPriorities = []; if (activeFocus) { topPriorities.push( `Focus on ${activeFocus.type}: ${activeFocus.identifier}` ); } if (currentIntent) { switch (currentIntent) { case "debugging": topPriorities.push("Identify and fix issues in the code"); break; case "feature_planning": topPriorities.push("Design and plan new features"); break; case "code_review": topPriorities.push("Review code for quality and correctness"); break; case "learning": topPriorities.push("Explain concepts and provide information"); break; case "code_generation": topPriorities.push("Generate or modify code"); break; default: topPriorities.push("Address user's current needs"); } } // Include active context items as priorities if available if (activeContext && activeContext.recentContextItems) { const priorityItems = activeContext.recentContextItems .slice(0, 2) .map((item) => { if (item.type === "file") { return `Maintain context on file: ${item.name || item.path}`; } else if (item.type === "entity") { return `Keep focus on: ${item.name}`; } return null; }) .filter(Boolean); topPriorities.push(...priorityItems); } return { summary: summaryText, topPriorities: topPriorities.length > 0 ? topPriorities : undefined, }; } catch (error) { logMessage("ERROR", `Error generating context synthesis`, { error: error.message, }); // Return minimal synthesis in case of error return { summary: "Context updated", }; } } // Export the tool definition for server registration export default { name: "update_conversation_context", description: "Updates an existing conversation context with new messages, code changes, and context management", inputSchema: updateConversationContextInputSchema, outputSchema: updateConversationContextOutputSchema, handler, };