cline-sdk
Version:
Comprehensive SDK for Cline - AI-powered development assistant with database integration, execution modes, and custom functions
1,181 lines (1,172 loc) âĸ 46.3 kB
JavaScript
"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: [] };
// Set defaults for optional properties
const maxAttempts = retryConfig.maxAttempts ?? 1;
const waitTimeMs = retryConfig.waitTimeMs ?? 0;
const exponentialBackoff = retryConfig.exponentialBackoff ?? false;
const retryOnErrors = retryConfig.retryOnErrors ?? [];
if (!retryConfig.enabled)
return;
const currentAttempts = this.taskRetryAttempts.get(this.currentTask.id) || 0;
const waitTime = exponentialBackoff
? waitTimeMs * Math.pow(2, currentAttempts)
: waitTimeMs;
const event = {
taskId: this.currentTask.id,
attemptNumber: currentAttempts + 1,
maxAttempts: 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 ?? 1);
}
/**
* 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 waitTimeMs = retryConfig.waitTimeMs ?? 0;
const exponentialBackoff = retryConfig.exponentialBackoff ?? false;
const waitTime = exponentialBackoff
? waitTimeMs * Math.pow(2, currentAttempts)
: 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 ?? 1}`, 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