UNPKG

cline-sdk

Version:

Comprehensive SDK for Cline - AI-powered development assistant with database integration, execution modes, and custom functions

1,174 lines (1,165 loc) â€ĸ 46 kB
"use strict"; /** * 🚀 COMPLETE CLINE SDK - Full Implementation * * This is the complete implementation with: * - Real Anthropic API integration * - All tools (Write, Read, Bash) * - Full configuration system * - Real file operations * - Task management */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CompleteClineSDK = void 0; const events_1 = require("events"); const uuid_1 = require("uuid"); // Import our components const cline_config_1 = require("./config/cline-config"); const anthropic_client_1 = require("./api/anthropic-client"); const write_tool_1 = require("./tools/write-tool"); const read_tool_1 = require("./tools/read-tool"); const bash_tool_1 = require("./tools/bash-tool"); const supabase_write_tool_1 = require("./tools/supabase-write-tool"); const supabase_read_tool_1 = require("./tools/supabase-read-tool"); const custom_function_tool_1 = require("./tools/custom-function-tool"); const function_registry_1 = require("./functions/function-registry"); const cost_calculator_1 = require("./utils/cost-calculator"); const postgres_manager_1 = require("./database/postgres-manager"); const database_query_tool_1 = require("./tools/database-query-tool"); const database_schema_tool_1 = require("./tools/database-schema-tool"); const database_rls_tool_1 = require("./tools/database-rls-tool"); const table_knowledge_1 = require("./database/table-knowledge"); const execution_modes_1 = require("./modes/execution-modes"); const prompt_optimizer_1 = require("./utils/prompt-optimizer"); // === MAIN SDK CLASS === class CompleteClineSDK extends events_1.EventEmitter { constructor(config) { super(); this.tools = new Map(); this.taskHistory = []; this.functionRegistry = new function_registry_1.FunctionRegistry(); this.tableKnowledgeManager = new table_knowledge_1.TableKnowledgeManager(); this.executionMode = "act"; // Default to full execution mode this.promptOptimizer = new prompt_optimizer_1.PromptOptimizer(); // === RETRY TRACKING === this.taskRetryAttempts = new Map(); // Initialize configuration if (typeof config === "string") { // Config is a path to config directory this.configManager = new cline_config_1.ClineConfigManager(config); } else { // Config is configuration object or undefined this.configManager = cline_config_1.ClineConfigManager.fromEnvironment(); if (config) { this.configManager.updateConfig(config); } } // Validate configuration const validation = this.configManager.validateConfig(); if (!validation.valid) { throw new Error(`Invalid configuration: ${validation.errors.join(", ")}`); } // Initialize API client this.anthropicClient = new anthropic_client_1.AnthropicClient(this.configManager.getConfig()); // Initialize tools this.initializeTools(); // Set initial execution mode if specified in config if (config && typeof config === "object" && "executionMode" in config) { this.executionMode = config.executionMode || "act"; } console.log("🚀 Complete Cline SDK initialized successfully"); console.log("📂 Working directory:", this.configManager.getConfig().workingDirectory); console.log("🔧 Enabled tools:", this.configManager.getConfig().enabledTools.join(", ")); console.log("âš™ī¸ Execution mode:", this.executionMode.toUpperCase()); } initializeTools() { const config = this.configManager.getConfig(); this.tools = new Map(); // Initialize storage-specific tools based on configuration if (config.storageType === "supabase" && config.supabase) { // Initialize Supabase tools const supabaseWriteTool = new supabase_write_tool_1.SupabaseWriteTool(config.supabase); const supabaseReadTool = new supabase_read_tool_1.SupabaseReadTool(config.supabase); this.tools.set("Write", supabaseWriteTool); this.tools.set("Read", supabaseReadTool); console.log("đŸŒŠī¸ Using Supabase storage backend"); } else { // Initialize local file system tools const writeTool = new write_tool_1.WriteTool(config.workingDirectory); const readTool = new read_tool_1.ReadTool(config.workingDirectory); this.tools.set("Write", writeTool); this.tools.set("Read", readTool); console.log("📁 Using local file system storage"); } // Bash tool is always local regardless of storage type const bashTool = new bash_tool_1.BashTool(config.workingDirectory); this.tools.set("Bash", bashTool); // Initialize custom function tool if enabled if (config.enableCustomFunctions) { this.customFunctionTool = new custom_function_tool_1.CustomFunctionTool(this.functionRegistry); this.tools.set("CustomFunction", this.customFunctionTool); console.log("đŸŽ¯ Custom function support enabled"); } // Initialize database tools if enabled if (config.enableDatabase && config.database) { this.initializeDatabaseTools(config); } } initializeDatabaseTools(config) { if (!config.database) { console.warn("âš ī¸ Database configuration missing, skipping database tools initialization"); return; } try { // Create PostgreSQL manager const postgresConfig = { host: config.database.host, port: config.database.port, database: config.database.database, username: config.database.username, password: config.database.password, ssl: config.database.ssl, maxConnections: config.database.maxConnections, idleTimeoutMillis: config.database.idleTimeoutMillis, connectionTimeoutMillis: config.database.connectionTimeoutMillis, }; this.databaseManager = new postgres_manager_1.PostgreSQLManager(postgresConfig, config.database.permissions); // Connect knowledge manager to database manager this.tableKnowledgeManager = new table_knowledge_1.TableKnowledgeManager(this.databaseManager); // Initialize database tools const queryTool = new database_query_tool_1.DatabaseQueryTool(this.databaseManager); const schemaTool = new database_schema_tool_1.DatabaseSchemaTool(this.databaseManager); const rlsTool = new database_rls_tool_1.DatabaseRLSTool(this.databaseManager); this.tools.set("DatabaseQuery", queryTool); this.tools.set("DatabaseSchema", schemaTool); this.tools.set("DatabaseRLS", rlsTool); console.log("🐘 PostgreSQL database tools initialized"); console.log(`📊 Database: ${config.database.host}:${config.database.port}/${config.database.database}`); console.log("🔧 Available database tools: DatabaseQuery, DatabaseSchema, DatabaseRLS"); // Test connection on initialization this.initDatabaseConnection(); } catch (error) { console.error("❌ Failed to initialize database tools:", error); } } async initDatabaseConnection() { if (!this.databaseManager) return; try { const result = await this.databaseManager.testConnection(); if (result.connected) { console.log("✅ Database connection successful"); console.log(`📋 PostgreSQL version: ${result.version}`); } else { console.error("❌ Database connection failed:", result.message); } } catch (error) { console.error("❌ Database connection test failed:", error); } } // === PUBLIC API === /** * Create a new task */ async createTask(text, images = [], files = []) { // Check for mode switch in the task text const modeSwitched = this.handleModeSwitch(text); const task = { id: (0, uuid_1.v4)(), created: Date.now(), text, images, files, status: "running", messages: [ { ts: Date.now(), type: "say", say: "user", text, images, }, ], tokensIn: 0, tokensOut: 0, totalCost: 0, costCalculations: [], }; this.currentTask = task; this.taskHistory.unshift(task); // Emit initial events this.emit("task:created", task); this.emit("state:changed", this.getState()); // Emit execution state as idle initially this.emitExecutionState("idle"); // Emit chat status about task creation this.emitChatResponse("status_update", `📝 New task created in ${this.executionMode.toUpperCase()} mode`, undefined, { isImportant: false }); // If mode was switched, acknowledge it first if (modeSwitched) { const modeMessage = { ts: Date.now(), type: "say", say: "assistant", text: `Mode switched to ${this.executionMode.toUpperCase()}.\n\n${this.getModeStatus()}`, }; task.messages.push(modeMessage); this.emit("message:added", modeMessage); } // Start processing the task this.processTask(task); return task; } /** * Send a response to the current task */ async sendResponse(text) { if (!this.currentTask) { throw new Error("No active task to respond to"); } if (this.currentTask.status !== "running") { throw new Error("Cannot respond to a task that is not running"); } // Check for mode switch in the response const modeSwitched = this.handleModeSwitch(text); // Add user message const message = { ts: Date.now(), type: "say", say: "user", text, }; this.currentTask.messages.push(message); this.emit("message:added", message); this.emit("task:updated", this.currentTask); // If mode was switched, acknowledge it first if (modeSwitched) { const modeMessage = { ts: Date.now(), type: "say", say: "assistant", text: `Mode switched to ${this.executionMode.toUpperCase()}.\n\n${this.getModeStatus()}`, }; this.currentTask.messages.push(modeMessage); this.emit("message:added", modeMessage); } // Continue processing this.processTask(this.currentTask); } /** * Get current state */ getState() { return { version: "1.0.0", currentTask: this.currentTask, taskHistory: this.taskHistory, config: this.configManager.getConfig(), workingDirectory: this.configManager.getConfig().workingDirectory, executionMode: this.executionMode, }; } /** * Update configuration */ updateConfig(updates) { this.configManager.updateConfig(updates); // Reinitialize tools with new config this.initializeTools(); // Recreate API client if needed const config = this.configManager.getConfig(); this.anthropicClient = new anthropic_client_1.AnthropicClient(config); this.emit("state:changed", this.getState()); } /** * Cancel current task */ cancelCurrentTask() { if (this.currentTask && this.currentTask.status === "running") { this.currentTask.status = "cancelled"; this.emit("task:updated", this.currentTask); this.emit("state:changed", this.getState()); } } // === CUSTOM FUNCTION REGISTRY API === /** * Register a custom function that can be called by the AI */ registerFunction(definition, implementation) { this.functionRegistry.register(definition, implementation); // Update the custom function tool to reflect new functions if (this.customFunctionTool) { this.tools.set("CustomFunction", this.customFunctionTool); } console.log(`đŸŽ¯ Custom function '${definition.name}' is now available to the AI`); } /** * Unregister a custom function */ unregisterFunction(name) { const result = this.functionRegistry.unregister(name); // Update the custom function tool if (this.customFunctionTool) { this.tools.set("CustomFunction", this.customFunctionTool); } return result; } /** * Get all registered custom functions */ getRegisteredFunctions() { return this.functionRegistry.getFunctionNames(); } /** * Get custom function registry stats */ getFunctionStats() { return this.functionRegistry.getStats(); } /** * Clear all registered functions */ clearFunctions() { this.functionRegistry.clear(); // Update the custom function tool if (this.customFunctionTool) { this.tools.set("CustomFunction", this.customFunctionTool); } } // === DATABASE API === /** * Get database connection status */ getDatabaseStatus() { if (!this.databaseManager) { return { connected: false, error: "Database not configured" }; } return { connected: true, config: this.databaseManager.getConnectionStatus(), }; } /** * Test database connection */ async testDatabaseConnection() { if (!this.databaseManager) { return { connected: false, message: "Database not configured" }; } return await this.databaseManager.testConnection(); } /** * Get database schema context for AI */ getDatabaseContext() { if (!this.databaseManager) { return "No database connected"; } return this.databaseManager.getSchemaContext(); } /** * Close database connections */ async closeDatabaseConnections() { if (this.databaseManager) { await this.databaseManager.close(); this.databaseManager = undefined; } } // === TABLE KNOWLEDGE API === /** * Register detailed business knowledge about a database table */ addTable(knowledge, options = {}) { this.tableKnowledgeManager.addTable(knowledge, options); } /** * Add detailed knowledge about a specific column */ addColumn(schemaName, tableName, columnKnowledge) { this.tableKnowledgeManager.addColumn(schemaName, tableName, columnKnowledge); } /** * Register multiple tables at once */ addTables(tables, options = {}) { this.tableKnowledgeManager.addTables(tables, options); } /** * Get registered knowledge for a specific table */ getTableKnowledge(schemaName, tableName) { return this.tableKnowledgeManager.getTableKnowledge(schemaName, tableName); } /** * Get all registered table knowledge */ getAllTableKnowledge() { return this.tableKnowledgeManager.getAllTables(); } /** * Search tables by domain, tags, or keywords */ searchTableKnowledge(query) { return this.tableKnowledgeManager.searchTables(query); } /** * Export all table knowledge as JSON */ exportTableKnowledge() { return this.tableKnowledgeManager.exportKnowledge(); } /** * Import table knowledge from JSON */ importTableKnowledge(jsonData) { this.tableKnowledgeManager.importKnowledge(jsonData); } /** * Clear all registered table knowledge */ clearTableKnowledge() { this.tableKnowledgeManager.clearKnowledge(); } /** * Get statistics about registered table knowledge */ getTableKnowledgeStats() { return this.tableKnowledgeManager.getStats(); } /** * Generate AI-friendly context from registered knowledge */ generateKnowledgeContext(includeOnlyTables) { return this.tableKnowledgeManager.generateKnowledgeContext(includeOnlyTables); } /** * Validate registered knowledge against actual database schema */ async validateTableKnowledge(schemaName, tableName) { return await this.tableKnowledgeManager.validateKnowledge(schemaName, tableName); } // === EXECUTION MODE API === /** * Get current execution mode */ getExecutionMode() { return this.executionMode; } /** * Set execution mode */ setExecutionMode(mode) { const previousMode = this.executionMode; this.executionMode = mode; console.log(`🔄 Execution mode changed: ${previousMode.toUpperCase()} → ${mode.toUpperCase()}`); this.emit("mode:changed", mode, previousMode); this.emit("state:changed", this.getState()); } /** * Get mode capabilities and restrictions */ getModeStatus() { return (0, execution_modes_1.generateModeStatus)(this.executionMode); } /** * Check if a specific tool is allowed in current mode */ isToolAllowedInMode(toolName) { return (0, execution_modes_1.isToolAllowed)(toolName, this.executionMode); } /** * Get restrictions for current mode */ getCurrentModeRestrictions() { return (0, execution_modes_1.getModeRestrictions)(this.executionMode); } /** * Parse and execute mode switch from user input */ handleModeSwitch(userInput) { const newMode = (0, execution_modes_1.parseModeSwitch)(userInput); if (newMode && newMode !== this.executionMode) { this.setExecutionMode(newMode); return true; } return false; } // === NEW EVENT HELPER METHODS === /** * Emit AI thinking event */ emitThinking(phase, content, context) { if (!this.currentTask) return; const event = { taskId: this.currentTask.id, phase, content, timestamp: Date.now(), context: { availableTools: Array.from(this.tools.keys()), currentMode: this.executionMode, ...context } }; this.emit("ai:thinking", event); if (phase === "reasoning") { this.emit("ai:reasoning", event); } } /** * Emit chat response event */ emitChatResponse(type, content, formatted, metadata) { if (!this.currentTask) return; const event = { taskId: this.currentTask.id, type, content, formatted, timestamp: Date.now(), metadata }; this.emit("chat:response", event); if (type === "status_update") { this.emit("chat:status", event); } } /** * Emit execution state change event */ emitExecutionState(state, details) { if (!this.currentTask) return; const event = { taskId: this.currentTask.id, state, details, timestamp: Date.now() }; this.emit("execution:state:changed", event); this.emit("execution:phase:changed", state, this.currentTask.id); } /** * Emit tool execution event */ emitToolExecution(toolName, phase, input, output, error, executionTime) { if (!this.currentTask) return; const event = { taskId: this.currentTask.id, toolName, phase, input, output, error, executionTime, timestamp: Date.now() }; switch (phase) { case "starting": this.emit("tool:starting", event); break; case "executing": this.emit("tool:executing", event); break; case "completed": this.emit("tool:completed", event); break; case "error": this.emit("tool:error", event); break; } } /** * Emit retry event */ emitRetryEvent(error, errorType, phase) { if (!this.currentTask) return; const config = this.configManager.getConfig(); const retryConfig = config.retry || { enabled: false, maxAttempts: 1, waitTimeMs: 0, exponentialBackoff: false, retryOnErrors: [] }; if (!retryConfig.enabled) return; const currentAttempts = this.taskRetryAttempts.get(this.currentTask.id) || 0; const waitTime = retryConfig.exponentialBackoff ? retryConfig.waitTimeMs * Math.pow(2, currentAttempts) : retryConfig.waitTimeMs; const event = { taskId: this.currentTask.id, attemptNumber: currentAttempts + 1, maxAttempts: retryConfig.maxAttempts, waitTimeMs: waitTime, error, errorType, timestamp: Date.now(), nextRetryAt: phase === "waiting" ? Date.now() + waitTime : undefined }; switch (phase) { case "attempt": this.emit("retry:attempt", event); break; case "waiting": this.emit("retry:waiting", event); break; case "failed": this.emit("retry:failed", event); break; } } /** * Check if error should be retried */ shouldRetryError(error, errorType) { const config = this.configManager.getConfig(); const retryConfig = config.retry; if (!retryConfig?.enabled) return false; if (!retryConfig.retryOnErrors.includes(errorType)) return false; if (!this.currentTask) return false; const currentAttempts = this.taskRetryAttempts.get(this.currentTask.id) || 0; return currentAttempts < retryConfig.maxAttempts; } /** * Wait for retry delay */ async waitForRetry() { if (!this.currentTask) return; const config = this.configManager.getConfig(); const retryConfig = config.retry; if (!retryConfig?.enabled) return; const currentAttempts = this.taskRetryAttempts.get(this.currentTask.id) || 0; const waitTime = retryConfig.exponentialBackoff ? retryConfig.waitTimeMs * Math.pow(2, currentAttempts) : retryConfig.waitTimeMs; // Emit waiting event this.emitRetryEvent("Waiting before retry attempt", "llm_error", "waiting"); this.emitChatResponse("status_update", `âŗ Waiting ${waitTime / 1000}s before retry attempt ${currentAttempts + 1}/${retryConfig.maxAttempts}`, undefined, { isImportant: true }); this.emitExecutionState("waiting_user", { estimatedTime: waitTime }); return new Promise(resolve => setTimeout(resolve, waitTime)); } /** * Increment retry attempt counter */ incrementRetryAttempt(taskId) { const currentAttempts = this.taskRetryAttempts.get(taskId) || 0; const newAttempts = currentAttempts + 1; this.taskRetryAttempts.set(taskId, newAttempts); return newAttempts; } /** * Reset retry attempts for task */ resetRetryAttempts(taskId) { this.taskRetryAttempts.delete(taskId); } /** * Classify error type for retry logic */ classifyError(error) { const errorMessage = typeof error === 'string' ? error : error.message; const errorString = errorMessage.toLowerCase(); if (errorString.includes('timeout') || errorString.includes('timed out')) { return "timeout_error"; } if (errorString.includes('api') || errorString.includes('rate limit') || errorString.includes('quota')) { return "api_error"; } if (errorString.includes('model') || errorString.includes('anthropic') || errorString.includes('llm')) { return "llm_error"; } if (errorString.includes('tool') || errorString.includes('execution')) { return "tool_error"; } return "unknown_error"; } // === COST TRACKING API === /** * Get cost information for current task */ getCurrentTaskCost() { return this.currentTask?.costCalculations || []; } /** * Get total cost for current task */ getCurrentTaskTotalCost() { return this.currentTask?.totalCost || 0; } /** * Get cost information for a specific task */ getTaskCost(taskId) { const task = this.taskHistory.find((t) => t.id === taskId); return task?.costCalculations || []; } /** * Get total costs for all tasks */ getTotalCosts() { let totalCost = 0; let totalTokensIn = 0; let totalTokensOut = 0; const costsByProvider = {}; const costsByModel = {}; for (const task of this.taskHistory) { totalCost += task.totalCost; totalTokensIn += task.tokensIn; totalTokensOut += task.tokensOut; for (const cost of task.costCalculations) { costsByProvider[cost.provider] = (costsByProvider[cost.provider] || 0) + cost.total_cost_usd; costsByModel[cost.model] = (costsByModel[cost.model] || 0) + cost.total_cost_usd; } } return { totalCost, taskCount: this.taskHistory.length, totalTokensIn, totalTokensOut, averageCostPerTask: this.taskHistory.length > 0 ? totalCost / this.taskHistory.length : 0, costsByProvider, costsByModel, }; } /** * Get pricing information for current provider/model */ getCurrentPricingInfo() { const config = this.configManager.getConfig(); const pricing = cost_calculator_1.CostCalculator.getPricingInfo(config.apiProvider, config.model || "default"); if (!pricing) return null; return { provider: config.apiProvider, model: config.model || "default", inputCostPer1k: pricing.input_cost_per_1k, outputCostPer1k: pricing.output_cost_per_1k, }; } /** * Set custom pricing for current provider/model */ setCustomPricing(inputCostPer1k, outputCostPer1k) { const config = this.configManager.getConfig(); cost_calculator_1.CostCalculator.setCustomPricing(config.apiProvider, config.model || "default", { input_cost_per_1k: inputCostPer1k, output_cost_per_1k: outputCostPer1k, }); } // === INTERNAL PROCESSING === async processTask(task) { if (task.status !== "running") return; try { // Emit thinking events this.emitThinking("analyzing", "Analyzing the user's request and available context"); this.emitExecutionState("thinking"); // Convert task messages to Anthropic format const anthropicMessages = this.convertToAnthropicMessages(task.messages); this.emitThinking("planning", "Determining which tools are needed and planning the approach"); // Get enabled tools const enabledTools = this.getEnabledToolDefinitions(); this.emitThinking("reasoning", "Processing with AI model to generate response and actions", { availableTools: enabledTools.map(t => t.name), constraints: [ `canExecuteTools: ${this.getCurrentModeRestrictions().canExecuteTools}`, `canWriteFiles: ${this.getCurrentModeRestrictions().canWriteFiles}`, `canRunCommands: ${this.getCurrentModeRestrictions().canRunCommands}` ] }); // Send to Anthropic API const response = await this.anthropicClient.sendMessage(anthropicMessages, enabledTools, this.buildSystemPrompt()); // Calculate cost for this API call const config = this.configManager.getConfig(); const costCalculation = cost_calculator_1.CostCalculator.calculateCost(config.apiProvider, config.model || "default", { input_tokens: response.usage.input_tokens, output_tokens: response.usage.output_tokens, total_tokens: response.usage.input_tokens + response.usage.output_tokens, }); // Update token usage and costs task.tokensIn += response.usage.input_tokens; task.tokensOut += response.usage.output_tokens; task.totalCost += costCalculation.total_cost_usd; task.costCalculations.push(costCalculation); console.log(`💰 API call cost: ${cost_calculator_1.CostCalculator.formatCost(costCalculation)} (${costCalculation.provider}/${costCalculation.model})`); console.log(`📊 Tokens: ${costCalculation.input_tokens} in, ${costCalculation.output_tokens} out`); // Process response content await this.processAnthropicResponse(task, response, costCalculation); // Reset retry attempts on successful processing this.resetRetryAttempts(task.id); } catch (error) { const errorObj = error instanceof Error ? error : new Error(String(error)); const errorType = this.classifyError(errorObj); console.error("❌ Task processing failed:", error); // Check if we should retry this error if (this.shouldRetryError(errorObj.message, errorType)) { const attemptNumber = this.incrementRetryAttempt(task.id); console.log(`🔄 Retry attempt ${attemptNumber} for task ${task.id}`); // Emit retry attempt event this.emitRetryEvent(errorObj.message, errorType, "attempt"); this.emitChatResponse("status_update", `🔄 Retrying after error (attempt ${attemptNumber})`, undefined, { isImportant: true }); // Wait before retry await this.waitForRetry(); // Retry the task this.emitThinking("reasoning", "Retrying task after error", { constraints: [`Previous error: ${errorObj.message}`, `Attempt: ${attemptNumber}`] }); return this.processTask(task); } else { // No more retries or error not retryable if (errorType !== "unknown_error") { this.emitRetryEvent(errorObj.message, errorType, "failed"); } task.status = "error"; // Emit error state this.emitExecutionState("error", { errorMessage: errorObj.message }); // Emit error chat response this.emitChatResponse("error", `I encountered an error: ${errorObj.message}`, undefined, { isImportant: true }); const errorMessage = { ts: Date.now(), type: "say", say: "assistant", text: `I encountered an error: ${errorObj.message}`, }; task.messages.push(errorMessage); this.emit("message:added", errorMessage); this.emit("task:error", task, errorObj); } } this.emit("task:updated", task); this.emit("state:changed", this.getState()); } async processAnthropicResponse(task, response, costCalculation) { this.emitThinking("evaluating", "Processing AI response and determining next actions"); for (const content of response.content) { if (content.type === "text" && content.text) { // Emit chat response event with formatted content this.emitChatResponse("response", content.text, { markdown: content.text, plainText: content.text.replace(/[*_`~]/g, '') // Simple markdown stripping }); // Add assistant message with cost information const message = { ts: Date.now(), type: "say", say: "assistant", text: content.text, costCalculation: costCalculation, }; task.messages.push(message); this.emit("message:added", message); } else if (content.type === "tool_use" && content.name && content.input) { // Set execution state to tool execution this.emitExecutionState("tool_execution", { currentTool: content.name }); // Execute tool await this.executeTool(task, content.name, content.input, content.id || (0, uuid_1.v4)()); } } // Check if task needs more processing const lastMessage = task.messages[task.messages.length - 1]; if (lastMessage?.type === "tool") { // Tool was executed, continue processing this.emitExecutionState("thinking"); setTimeout(() => this.processTask(task), 100); } else { // Task completed task.status = "completed"; // Clean up retry attempts this.resetRetryAttempts(task.id); this.emitExecutionState("completed"); this.emitChatResponse("status_update", "Task completed successfully", undefined, { isImportant: true }); this.emit("task:completed", task); } } async executeTool(task, toolName, input, toolId) { // Emit tool starting event this.emitToolExecution(toolName, "starting", input); // Check if tool is allowed in current mode if (!this.isToolAllowedInMode(toolName)) { console.log(`đŸšĢ Tool ${toolName} blocked by ${this.executionMode.toUpperCase()} mode`); const errorMessage = `Tool ${toolName} is not allowed in ${this.executionMode.toUpperCase()} mode. Available tools: ${execution_modes_1.MODE_DEFINITIONS[this.executionMode].allowedTools.join(", ") || "None"}`; // Emit tool error event this.emitToolExecution(toolName, "error", input, undefined, errorMessage); // Emit chat status update this.emitChatResponse("status_update", `âš ī¸ Tool ${toolName} blocked by current execution mode`, undefined, { isImportant: true }); const errorResult = { success: false, error: errorMessage, mode_restriction: true, current_mode: this.executionMode, allowed_tools: execution_modes_1.MODE_DEFINITIONS[this.executionMode].allowedTools, }; const toolMessage = { ts: Date.now(), type: "tool", toolName, toolInput: input, toolResult: errorResult, }; task.messages.push(toolMessage); this.emit("message:added", toolMessage); return; } const tool = this.tools.get(toolName); if (!tool) { const errorMessage = `Tool not found: ${toolName}`; this.emitToolExecution(toolName, "error", input, undefined, errorMessage); throw new Error(errorMessage); } console.log(`🔧 Executing tool: ${toolName} (${this.executionMode.toUpperCase()} mode)`); console.log(`đŸ“Ĩ Input:`, input); // Emit executing status this.emitToolExecution(toolName, "executing", input); this.emitChatResponse("status_update", `🔧 Executing ${toolName}...`); const startTime = Date.now(); try { const result = await tool.execute(input); const executionTime = Date.now() - startTime; console.log(`📤 Result:`, result); // Emit tool completed event this.emitToolExecution(toolName, "completed", input, result, undefined, executionTime); // Add tool message const toolMessage = { ts: Date.now(), type: "tool", toolName, toolInput: input, toolResult: result, }; task.messages.push(toolMessage); this.emit("message:added", toolMessage); } catch (error) { const executionTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : "Unknown error"; console.error(`❌ Tool execution failed:`, error); // Emit tool error event this.emitToolExecution(toolName, "error", input, undefined, errorMessage, executionTime); // Emit error chat response this.emitChatResponse("error", `❌ Tool ${toolName} failed: ${errorMessage}`, undefined, { isImportant: true }); const errorResult = { success: false, error: errorMessage, }; const toolMessage = { ts: Date.now(), type: "tool", toolName, toolInput: input, toolResult: errorResult, }; task.messages.push(toolMessage); this.emit("message:added", toolMessage); } } convertToAnthropicMessages(messages) { const anthropicMessages = []; for (const msg of messages) { if (msg.type === "say" && msg.say && msg.text) { anthropicMessages.push({ role: msg.say === "user" ? "user" : "assistant", content: msg.text, }); } else if (msg.type === "tool" && msg.toolName && msg.toolResult) { // Add tool result to conversation anthropicMessages.push({ role: "assistant", content: `Tool ${msg.toolName} executed: ${JSON.stringify(msg.toolResult)}`, }); } } return anthropicMessages; } getEnabledToolDefinitions() { const config = this.configManager.getConfig(); const definitions = []; // In ASK mode, no tools should be available to the AI if (this.executionMode === "ask") { return []; } for (const toolName of config.enabledTools) { // Check if tool is allowed in current mode if (!this.isToolAllowedInMode(toolName)) { continue; } const tool = this.tools.get(toolName); if (tool && tool.getDefinition) { definitions.push(tool.getDefinition()); } } // Always include database tools if enabled (subject to mode restrictions) if (config.enableDatabase && this.databaseManager) { const dbTools = ["DatabaseQuery", "DatabaseSchema", "DatabaseRLS"]; for (const toolName of dbTools) { if (!this.isToolAllowedInMode(toolName)) { continue; } const tool = this.tools.get(toolName); if (tool && tool.getDefinition) { definitions.push(tool.getDefinition()); } } } return definitions; } buildSystemPrompt() { const config = this.configManager.getConfig(); const modeConfig = execution_modes_1.MODE_DEFINITIONS[this.executionMode]; let prompt = `You are Cline, an AI assistant that can read and write files, run commands, and help with software development tasks. Current working directory: ${config.workingDirectory} ${modeConfig.systemPromptAddition}`; // Add database information if available if (config.enableDatabase && config.database && this.databaseManager) { prompt += ` Database Connection: ${config.database.host}:${config.database.port}/${config.database.database} Database Permissions: - Read: ${config.database.permissions.allowRead ? "✅" : "❌"} - Write: ${config.database.permissions.allowWrite ? "✅" : "❌"} - Delete: ${config.database.permissions.allowDelete ? "✅" : "❌"} - Schema Changes: ${config.database.permissions.allowSchemaChanges ? "✅" : "❌"} - RLS Management: ${config.database.permissions.allowRLSManagement ? "✅" : "❌"}`; if (config.database.permissions.restrictedTables.length > 0) { prompt += ` Restricted Tables: ${config.database.permissions.restrictedTables.join(", ")}`; } if (config.database.permissions.allowedTables.length > 0) { prompt += ` Allowed Tables: ${config.database.permissions.allowedTables.join(", ")}`; } // Add table knowledge if available const knowledgeContext = this.tableKnowledgeManager.generateKnowledgeContext(); if (knowledgeContext.length > 100) { // Only add if there's actual knowledge prompt += ` ${knowledgeContext}`; } } prompt += ` You have access to the following tools: `; // Include regular enabled tools for (const toolName of config.enabledTools) { const tool = this.tools.get(toolName); if (tool && tool.getDefinition) { const def = tool.getDefinition(); prompt += `- ${def.name}: ${def.description}\n`; } } // Automatically include database tools if database is enabled if (config.enableDatabase && this.databaseManager) { const dbTools = ["DatabaseQuery", "DatabaseSchema", "DatabaseRLS"]; for (const toolName of dbTools) { const tool = this.tools.get(toolName); if (tool && tool.getDefinition) { const def = tool.getDefinition(); prompt += `- ${def.name}: ${def.description}\n`; } } prompt += ` Database Usage Guidelines: - Always use DatabaseSchema tool first to understand the database structure - Use DatabaseQuery tool to execute SQL queries with proper safety checks - Use DatabaseRLS tool to manage Row Level Security policies when needed - Be careful with write/delete operations - respect the configured permissions - Use parameterized queries ($1, $2, etc.) to prevent SQL injection`; } prompt += ` Always use the appropriate tools to accomplish tasks. Be precise and helpful.`; if (config.customInstructions) { prompt += ` Custom Instructions: ${config.customInstructions}`; } return prompt; } // === PROMPT OPTIMIZATION METHODS === /** * Analyze a prompt for quality and provide optimization suggestions */ analyzePrompt(prompt) { this.emit("prompt:optimization:started", prompt); const result = this.promptOptimizer.optimizePrompt(prompt); this.emit("prompt:optimization:completed", result); return result; } /** * Get formatted optimization results for display */ formatOptimizationResults(result) { return this.promptOptimizer.formatResults(result); } /** * Check if a prompt can be optimized (using special command syntax) */ checkForOptimizationRequest(userInput) { const optimizePatterns = [ /^\/optimize\s+(.+)$/i, /^\/improve\s+(.+)$/i, /^optimize:\s*(.+)$/i, /^improve prompt:\s*(.+)$/i ]; for (const pattern of optimizePatterns) { const match = userInput.match(pattern); if (match) { return { shouldOptimize: true, extractedPrompt: match[1].trim() }; } } return { shouldOptimize: false }; } /** * Process optimization request and return improved prompt */ async handleOptimizationRequest(userInput) { const { shouldOptimize, extractedPrompt } = this.checkForOptimizationRequest(userInput); if (!shouldOptimize || !extractedPrompt) { return userInput; } const optimization = this.analyzePrompt(extractedPrompt); // Return formatted results return `🔧 Prompt Optimization Results: ${this.formatOptimizationResults(optimization)} You can now use the improved prompt for better results.`; } } exports.CompleteClineSDK = CompleteClineSDK; // === EXPORT === exports.default = CompleteClineSDK; //# sourceMappingURL=complete-cline-sdk.js.map