Skip to main content
Glama
notebook-run-cell.ts8.21 kB
/** * Notebook Run Cell Operation * * Executes code cells in notebooks and captures output */ import { BaseOperation, type OperationContext, type OperationResult } from '../base.js'; import { NotebookCreateOperation } from './notebook-create.js'; export class NotebookRunCellOperation extends BaseOperation { name = 'notebook_run_cell'; category = 'notebook'; async execute(context: OperationContext): Promise<OperationResult> { const { sessionState, prompt, parameters } = context; // Extract execution parameters const notebookId = this.getParam(parameters, 'notebookId', ''); const cellId = this.getParam(parameters, 'cellId', ''); const timeoutMs = this.getParam(parameters, 'timeoutMs', 5000); // Validate notebook exists const notebookStore = NotebookCreateOperation.getNotebookStore(); let notebook = notebookStore.getNotebook(notebookId); // If no notebook ID provided or notebook not found, try to find by session if (!notebook) { notebook = notebookStore.getNotebookBySession(sessionState.sessionId); if (!notebook) { throw new Error('No notebook found. Create a notebook first using notebook_create.'); } } // Find cell to execute let targetCell; if (cellId) { targetCell = notebook.cells.find(cell => cell.id === cellId); if (!targetCell) { throw new Error(`Cell with ID ${cellId} not found in notebook`); } } else { // If no cell ID provided, try to infer from prompt or use last code cell targetCell = this.findTargetCell(notebook, prompt); if (!targetCell) { throw new Error('No executable code cell found. Please specify cellId or add a code cell first.'); } } // Validate cell is executable if (targetCell.type !== 'code') { throw new Error(`Cell ${targetCell.id} is not a code cell (type: ${targetCell.type})`); } // Execute cell const execution = await notebookStore.executeCell( notebook.id, targetCell.id, timeoutMs ); // Analyze execution results const analysis = this.analyzeExecution(execution, targetCell); // Session tracking is managed by notebook store return this.createResult({ executionId: execution.id, cellId: targetCell.id, notebookId: notebook.id, status: execution.status, duration: execution.completedAt ? execution.completedAt - execution.startedAt : null, outputs: execution.outputs, error: execution.error, analysis, cell: { id: targetCell.id, type: targetCell.type, language: targetCell.language, source: targetCell.source, status: targetCell.status }, notebook: { id: notebook.id, title: notebook.metadata?.title || 'Untitled Notebook', totalExecutions: notebook.executions.size, cellCount: notebook.cells.length }, sessionContext: { sessionId: sessionState.sessionId, stats: sessionState.getStats(), notebookCount: 1 // Using notebook store's internal tracking }, instructions: { outputs: 'Check outputs array for stdout, stderr, and result data', debugging: execution.error ? 'Review error message and check code syntax' : 'Execution completed successfully', continuation: 'Add more cells or modify existing ones to continue analysis' } }); } /** * Find target cell when cellId not specified */ private findTargetCell(notebook: any, prompt: string) { const codeCells = notebook.cells.filter((cell: any) => cell.type === 'code'); if (codeCells.length === 0) { return null; } // If prompt mentions running specific code, try to find matching cell if (prompt.toLowerCase().includes('run') || prompt.toLowerCase().includes('execute')) { // Look for keywords in cell source const keywords = this.extractKeywords(prompt); for (const cell of codeCells) { if (keywords.some(keyword => cell.source.toLowerCase().includes(keyword))) { return cell; } } } // Default to last code cell return codeCells[codeCells.length - 1]; } /** * Extract keywords from prompt for cell matching */ private extractKeywords(prompt: string): string[] { const words = prompt.toLowerCase() .split(/\s+/) .filter(word => word.length > 3 && !['this', 'that', 'with', 'from', 'they', 'have', 'will', 'been'].includes(word)); return words.slice(0, 5); // Limit to first 5 meaningful words } /** * Analyze execution results */ private analyzeExecution(execution: any, cell: any) { const analysis: any = { success: execution.status === 'complete', duration: execution.completedAt - execution.startedAt, outputCount: execution.outputs.length, outputTypes: this.categorizeOutputs(execution.outputs), codeMetrics: this.analyzeCode(cell.source) }; if (execution.error) { analysis.errorAnalysis = this.analyzeError(execution.error, cell.source); } if (execution.outputs.length > 0) { analysis.outputSummary = this.summarizeOutputs(execution.outputs); } return analysis; } /** * Categorize outputs by type */ private categorizeOutputs(outputs: any[]): Record<string, number> { const categories: Record<string, number> = {}; outputs.forEach(output => { categories[output.type] = (categories[output.type] || 0) + 1; }); return categories; } /** * Analyze code characteristics */ private analyzeCode(source: string) { return { lineCount: source.split('\n').length, characterCount: source.length, hasLoops: /\b(for|while|forEach)\b/.test(source), hasFunctions: /\b(function|=>)\b/.test(source), hasVariables: /\b(let|const|var)\b/.test(source), hasConsoleOutput: /console\.(log|error|warn|info)/.test(source) }; } /** * Analyze error for common issues */ private analyzeError(error: string, source: string) { const analysis: any = { errorType: this.classifyError(error), likelyIssues: [], suggestions: [] }; // Common error patterns if (error.includes('ReferenceError')) { analysis.likelyIssues.push('Undefined variable or function'); analysis.suggestions.push('Check variable names and declarations'); } if (error.includes('SyntaxError')) { analysis.likelyIssues.push('Invalid JavaScript syntax'); analysis.suggestions.push('Review code syntax, brackets, and semicolons'); } if (error.includes('TypeError')) { analysis.likelyIssues.push('Type mismatch or null/undefined access'); analysis.suggestions.push('Check data types and null checks'); } if (error.includes('timeout')) { analysis.likelyIssues.push('Code execution took too long'); analysis.suggestions.push('Optimize loops or increase timeout'); } return analysis; } /** * Classify error type */ private classifyError(error: string): string { if (error.includes('ReferenceError')) return 'reference'; if (error.includes('SyntaxError')) return 'syntax'; if (error.includes('TypeError')) return 'type'; if (error.includes('timeout')) return 'timeout'; if (error.includes('RangeError')) return 'range'; return 'unknown'; } /** * Summarize outputs */ private summarizeOutputs(outputs: any[]) { const summary: any = { totalLines: 0, hasResults: false, hasErrors: false, hasConsoleOutput: false }; outputs.forEach(output => { summary.totalLines += output.data.split('\n').length; switch (output.type) { case 'result': summary.hasResults = true; break; case 'stderr': summary.hasErrors = true; break; case 'stdout': summary.hasConsoleOutput = true; break; } }); return summary; } } export default new NotebookRunCellOperation();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/waldzellai/clearthought-onepointfive'

If you have feedback or need assistance with the MCP directory API, please join our Discord server