diff --git a/src/nodes/generate-message.ts b/src/nodes/generate-message.ts index 7b36daff..de0074ec 100644 --- a/src/nodes/generate-message.ts +++ b/src/nodes/generate-message.ts @@ -3,6 +3,9 @@ import { loadModel, Task } from "../utils/load-model.js"; import { shellTool, applyPatchTool } from "../tools/index.js"; import { formatPlanPrompt } from "../utils/plan-prompt.js"; import { pauseSandbox } from "../utils/sandbox.js"; +import { createLogger, LogLevel } from "../utils/logger.js"; + +const logger = createLogger(LogLevel.INFO, "GenerateMessageNode"); const systemPrompt = `You are operating as a terminal-based agentic coding assistant built by LangChain. It wraps LLM models to enable natural language interaction with a local codebase. You are expected to be precise, safe, and helpful. @@ -81,10 +84,14 @@ export async function generateAction( // No tool calls means the graph is going to end. Pause the sandbox. let newSandboxSessionId: string | undefined; if (!hasToolCalls && state.sandboxSessionId) { - console.log("No tool calls found. Pausing sandbox..."); + logger.info("No tool calls found. Pausing sandbox..."); newSandboxSessionId = await pauseSandbox(state.sandboxSessionId); } + logger.info("Generated action", { + name: response.tool_calls?.[0].name, + args: response.tool_calls?.[0].args, + }); return { messages: [response], ...(newSandboxSessionId && { sandboxSessionId: newSandboxSessionId }), diff --git a/src/nodes/initialize.ts b/src/nodes/initialize.ts index a8d49554..81c386db 100644 --- a/src/nodes/initialize.ts +++ b/src/nodes/initialize.ts @@ -1,4 +1,5 @@ import { Sandbox } from "@e2b/code-interpreter"; +import { createLogger, LogLevel } from "../utils/logger.js"; import { GraphState, GraphConfig, @@ -14,6 +15,8 @@ import { } from "../utils/git/index.js"; import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js"; +const logger = createLogger(LogLevel.INFO, "Initialize"); + const SANDBOX_TEMPLATE_ID = "eh0860emqx28qyxmbctu"; async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) { @@ -32,7 +35,7 @@ async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) { gitCloneCommand.push(repoUrlWithToken); } - console.log("Cloning repository...", { + logger.info("Cloning repository", { command: gitCloneCommand.join(" "), }); return await sandbox.commands.run( @@ -41,7 +44,7 @@ async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) { ); } catch (e) { const errorFields = getSandboxErrorFields(e); - console.error("Failed to clone repository", errorFields ?? e); + logger.error("Failed to clone repository", errorFields ?? e); throw e; } } @@ -63,7 +66,7 @@ export async function initialize( if (sandboxSessionId) { try { - console.log("Sandbox session ID exists. Resuming...", { + logger.info("Sandbox session ID exists. Resuming", { sandboxSessionId, }); // Resume the sandbox if the session ID is in the config. @@ -76,7 +79,7 @@ export async function initialize( }; } catch (e) { // Error thrown, log it and continue. Will create a new sandbox session since the resumption failed. - console.error("Failed to get sandbox session.", e); + logger.error("Failed to get sandbox session", e); } } @@ -88,7 +91,7 @@ export async function initialize( ); } - console.log("Creating sandbox..."); + logger.info("Creating sandbox..."); const sandbox = await Sandbox.create( SANDBOX_TEMPLATE_ID, TIMEOUT_EXTENSION_OPT, @@ -97,16 +100,16 @@ export async function initialize( const res = await cloneRepo(sandbox, target_repository); if (res.error) { // TODO: This should probably be an interrupt. - console.error("Failed to clone repository.", res.error); + logger.error("Failed to clone repository", res.error); throw new Error(`Failed to clone repository.\n${res.error}`); } - console.log("Repository cloned successfully."); + logger.info("Repository cloned successfully."); const absoluteRepoDir = getRepoAbsolutePath(config); - console.log(`Configuring git user for repository at "${absoluteRepoDir}"...`); + logger.info(`Configuring git user for repository at "${absoluteRepoDir}"...`); await configureGitUserInRepo(absoluteRepoDir, sandbox); - console.log("Git user configured successfully."); + logger.info("Git user configured successfully."); const checkoutBranchRes = await checkoutBranch( absoluteRepoDir, @@ -116,7 +119,7 @@ export async function initialize( if (!checkoutBranchRes) { // TODO: This should probably be an interrupt. - console.error("\nFailed to checkout branch."); + logger.error("Failed to checkout branch."); throw new Error("Failed to checkout branch"); } diff --git a/src/nodes/progress-plan-step.ts b/src/nodes/progress-plan-step.ts index 9afd6f74..69df3856 100644 --- a/src/nodes/progress-plan-step.ts +++ b/src/nodes/progress-plan-step.ts @@ -1,8 +1,11 @@ import { z } from "zod"; +import { createLogger, LogLevel } from "../utils/logger.js"; import { GraphConfig, GraphState, GraphUpdate, PlanItem } from "../types.js"; import { loadModel, Task } from "../utils/load-model.js"; import { formatPlanPrompt } from "../utils/plan-prompt.js"; +const logger = createLogger(LogLevel.INFO, "ProgressPlanStep"); + const systemPrompt = `You are operating as a terminal-based agentic coding assistant built by LangChain. It wraps LLM models to enable natural language interaction with a local codebase. You are expected to be precise, safe, and helpful. In your workflow, you generate a plan, then act on said plan. It may take many actions to complete a single step, or a single action to complete the step. @@ -65,7 +68,7 @@ export async function progressPlanStep( const remainingTask = state.plan.find((p) => !p.completed); if (!remainingTask) { // No remaining tasks, end the process - console.log( + logger.info( "Found no remaining tasks in the plan during the check plan step.", ); return {}; diff --git a/src/nodes/take-action.ts b/src/nodes/take-action.ts index eab06b25..7e49a81e 100644 --- a/src/nodes/take-action.ts +++ b/src/nodes/take-action.ts @@ -1,4 +1,5 @@ import { isAIMessage, ToolMessage } from "@langchain/core/messages"; +import { createLogger, LogLevel } from "../utils/logger.js"; import { applyPatchTool, shellTool } from "../tools/index.js"; import { GraphState, GraphConfig, GraphUpdate } from "../types.js"; import { @@ -8,6 +9,8 @@ import { } from "../utils/git/index.js"; import { Sandbox } from "@e2b/code-interpreter"; +const logger = createLogger(LogLevel.INFO, "TakeAction"); + export async function takeAction( state: GraphState, config: GraphConfig, @@ -45,7 +48,11 @@ export async function takeAction( // @ts-expect-error tool.invoke types are weird here... result = await tool.invoke(toolCall.args); } catch (e) { - console.error("\nFailed to call tool", e); + logger.error("Failed to call tool", { + ...(e instanceof Error + ? { name: e.name, message: e.message, stack: e.stack } + : { error: e }), + }); const errMessage = e instanceof Error ? e.message : "Unknown error"; result = `FAILED TO CALL TOOL: "${toolCall.name}"\n\nError: ${errMessage}`; } @@ -66,7 +73,7 @@ export async function takeAction( let branchName: string | undefined = state.branchName; if (changedFiles.length > 0) { - console.log(`\nHas ${changedFiles.length} changed files. Committing...`, { + logger.info(`Has ${changedFiles.length} changed files. Committing.`, { changedFiles, }); branchName = await checkoutBranchAndCommit(config, sandbox, { diff --git a/src/tools/apply-patch.ts b/src/tools/apply-patch.ts index ad2887fc..64ac2b0a 100644 --- a/src/tools/apply-patch.ts +++ b/src/tools/apply-patch.ts @@ -6,6 +6,9 @@ import { Sandbox } from "@e2b/code-interpreter"; import { readFile, writeFile } from "../utils/read-write.js"; import { getCurrentTaskInput } from "@langchain/langgraph"; import { fixGitPatch } from "../utils/diff.js"; +import { createLogger, LogLevel } from "../utils/logger.js"; + +const logger = createLogger(LogLevel.INFO, "ApplyPatchTool"); const applyPatchToolSchema = z.object({ diff: z.string().describe("The diff to apply. Use a standard diff format."), @@ -17,7 +20,7 @@ export const applyPatchTool = tool( const state = getCurrentTaskInput(); const { sandboxSessionId } = state; if (!sandboxSessionId) { - console.error("FAILED TO RUN COMMAND: No sandbox session ID provided", { + logger.error("FAILED TO RUN COMMAND: No sandbox session ID provided", { input, }); throw new Error("FAILED TO RUN COMMAND: No sandbox session ID provided"); @@ -32,37 +35,46 @@ export const applyPatchTool = tool( file_path, ); if (!readFileSuccess) { + logger.error("Failed to read file", readFileOutput); return readFileOutput; } - console.log(`\nApplying patch to file ${file_path}\n`); - console.log("\nreadFileOutput\n", readFileOutput); - console.log("\ndiff\n", diff); - let patchedContent: string | false; try { + logger.info(`Applying patch to file ${file_path}`); const fixedDiff = fixGitPatch(diff, { [file_path]: readFileOutput, }); - console.log("\n\nfixedDiff\n\n", fixedDiff); patchedContent = applyPatch(readFileOutput, fixedDiff); } catch (e) { - console.error("Failed to apply patch", e); + logger.error("Failed to apply patch", { + ...(e instanceof Error + ? { name: e.name, message: e.message, stack: e.stack } + : { error: e }), + }); const errMessage = e instanceof Error ? e.message : "Unknown error"; return `FAILED TO APPLY PATCH: The diff could not be applied to file '${file_path}'.\n\nError: ${errMessage}`; } if (patchedContent === false) { + logger.error( + `FAILED TO APPLY PATCH: The diff could not be applied to file '${file_path}'. This may be due to an invalid diff format or conflicting changes with the file's current content. Original content length: ${readFileOutput.length}, Diff: ${diff.substring(0, 100)}...`, + ); return `FAILED TO APPLY PATCH: The diff could not be applied to file '${file_path}'. This may be due to an invalid diff format or conflicting changes with the file's current content. Original content length: ${readFileOutput.length}, Diff: ${diff.substring(0, 100)}...`; } - // TODO: Should we be committing every time we apply a diff? const { success: writeFileSuccess, output: writeFileOutput } = await writeFile(sandbox, file_path, patchedContent); if (!writeFileSuccess) { + logger.error("Failed to write file", { + writeFileOutput, + }); return writeFileOutput; } + logger.info( + `Successfully applied diff to \`${file_path}\` and saved changes.`, + ); return `Successfully applied diff to \`${file_path}\` and saved changes.`; }, { diff --git a/src/tools/shell.ts b/src/tools/shell.ts index b3064016..3cc82a20 100644 --- a/src/tools/shell.ts +++ b/src/tools/shell.ts @@ -4,8 +4,9 @@ import { Sandbox } from "@e2b/code-interpreter"; import { GraphState } from "../types.js"; import { getCurrentTaskInput } from "@langchain/langgraph"; import { TIMEOUT_MS } from "../constants.js"; +import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js"; -const DEFAULT_COMMAND_TIMEOUT = 120_000; // 2 minutes +const DEFAULT_COMMAND_TIMEOUT = 60_000; // 1 minute const shellToolSchema = z.object({ command: z.array(z.string()).describe("The command to run"), @@ -55,26 +56,28 @@ export const shellTool = tool( } return result.stdout; - } catch (e: any) { - if (typeof e === "object" && "result" in e && e.result) { + } catch (e) { + const errorFields = getSandboxErrorFields(e); + if (errorFields) { console.error("Failed to run command", { - error: e.message, - error_result: e.result, input, + error: errorFields, }); - return ( - "FAILED TO RUN COMMAND: " + - e.message + - "\n" + - JSON.stringify(e.result, null, 2) - ); + return `Command failed. Exit code: ${errorFields.exitCode}\nError: ${errorFields.error}\nStderr:\n${errorFields.stderr}\nStdout:\n${errorFields.stdout}`; } - console.error("Failed to run command: " + e.message, { - error: e, - input, - }); - throw new Error("FAILED TO RUN COMMAND: " + e.message); + console.error( + "Failed to run command: " + + (e instanceof Error ? e.message : "Unknown error"), + { + error: e, + input, + }, + ); + throw new Error( + "FAILED TO RUN COMMAND: " + + (e instanceof Error ? e.message : "Unknown error"), + ); } }, { diff --git a/src/utils/diff.ts b/src/utils/diff.ts index 29a4c4b6..e1b5c36f 100644 --- a/src/utils/diff.ts +++ b/src/utils/diff.ts @@ -1,3 +1,7 @@ +import { createLogger, LogLevel } from "./logger.js"; + +const logger = createLogger(LogLevel.INFO, "DiffUtil"); + interface Hunk { oldStart: number; oldLines: number; @@ -386,8 +390,12 @@ export function fixGitPatch( } return result; - } catch (error: any) { - console.error("Error fixing patch:", error); + } catch (e) { + logger.error(`Error fixing patch:`, { + ...(e instanceof Error + ? { name: e.name, message: e.message, stack: e.stack } + : { error: e }), + }); return patchString; } } diff --git a/src/utils/git/index.ts b/src/utils/git/index.ts index 5383f2b4..b7870b15 100644 --- a/src/utils/git/index.ts +++ b/src/utils/git/index.ts @@ -1,8 +1,11 @@ import { CommandResult, Sandbox } from "@e2b/code-interpreter"; +import { createLogger, LogLevel } from "../logger.js"; import { GraphConfig } from "../../types.js"; import { TIMEOUT_MS } from "../../constants.js"; import { getSandboxErrorFields } from "../sandbox-error-fields.js"; +const logger = createLogger(LogLevel.INFO, "GitUtil"); + export function getRepoAbsolutePath(config: GraphConfig): string { const repoName = config.configurable?.target_repository.repo; if (!repoName) { @@ -26,9 +29,7 @@ export async function checkoutBranch( branchName: string, sandbox: Sandbox, ): Promise { - console.log("\nChecking out branch...", { - branchName, - }); + logger.info(`Checking out branch '${branchName}'...`); try { const getCurrentBranchOutput = await sandbox.commands.run( @@ -38,11 +39,13 @@ export async function checkoutBranch( await sandbox.setTimeout(TIMEOUT_MS); if (getCurrentBranchOutput.exitCode !== 0) { - console.error("Failed to get current branch", getCurrentBranchOutput); + logger.error(`Failed to get current branch`, { + getCurrentBranchOutput, + }); } else { const currentBranch = getCurrentBranchOutput.stdout.trim(); if (currentBranch === branchName) { - console.log(`\nAlready on branch '${branchName}'. No checkout needed.`); + logger.info(`Already on branch '${branchName}'. No checkout needed.`); return { stdout: `Already on branch ${branchName}`, stderr: "", @@ -52,15 +55,22 @@ export async function checkoutBranch( } } catch (e) { const errorFields = getSandboxErrorFields(e); - console.error("Failed to get current branch", errorFields ?? e); + logger.error(`Failed to get current branch`, { + ...(errorFields && { errorFields }), + ...(e instanceof Error && { + name: e.name, + message: e.message, + stack: e.stack, + }), + }); return false; } let checkoutCommand: string; try { - console.log("\nChecking if branch exists...", { - command: `git rev-parse --verify --quiet "refs/heads/${branchName}"`, - }); + logger.info( + `Checking if branch 'refs/heads/${branchName}' exists using 'git rev-parse --verify --quiet'`, + ); // Check if branch exists using git rev-parse for robustness const checkBranchExistsOutput = await sandbox.commands.run( `git rev-parse --verify --quiet "refs/heads/${branchName}"`, @@ -85,7 +95,13 @@ export async function checkoutBranch( ) { checkoutCommand = `git checkout -b "${branchName}"`; } else { - console.error("\nError checking if branch exists", e); + logger.error(`Error checking if branch exists`, { + ...(e instanceof Error && { + name: e.name, + message: e.message, + stack: e.stack, + }), + }); return false; } } @@ -96,19 +112,27 @@ export async function checkoutBranch( }); if (gitCheckoutOutput.exitCode !== 0) { - console.error("\nFailed to checkout branch", gitCheckoutOutput); + logger.error(`Failed to checkout branch`, { + gitCheckoutOutput, + }); return false; } - console.log("\nChecked out branch successfully.", { - branchName, - gitCheckoutOutput: gitCheckoutOutput.stdout, + logger.info(`Checked out branch '${branchName}' successfully.`, { + gitCheckoutOutput, }); return gitCheckoutOutput; } catch (e) { const errorFields = getSandboxErrorFields(e); - console.error("Error checking out branch", errorFields ?? e); + logger.error(`Error checking out branch`, { + ...(errorFields && { errorFields }), + ...(e instanceof Error && { + name: e.name, + message: e.message, + stack: e.stack, + }), + }); return false; } } @@ -126,7 +150,7 @@ async function getGitUserDetailsFromGitHub(): Promise<{ }> { const githubToken = process.env.GITHUB_PAT; if (!githubToken) { - console.warn( + logger.warn( "GITHUB_PAT environment variable is not set. Cannot fetch user details from GitHub.", ); return {}; @@ -141,9 +165,9 @@ async function getGitUserDetailsFromGitHub(): Promise<{ }); if (!response.ok) { - console.error( - `Failed to fetch GitHub user info: ${response.status} ${response.statusText}. Response: ${await response.text()}`, - ); + logger.error(`Failed to fetch GitHub user info`, { + response, + }); return {}; } @@ -161,14 +185,20 @@ async function getGitUserDetailsFromGitHub(): Promise<{ const finalUserEmail = fetchedUserEmail || undefined; if (!finalUserName) { - console.warn("Could not determine GitHub username from API response."); + logger.warn("Could not determine GitHub username from API response."); } if (!finalUserEmail) { - console.warn("Could not determine GitHub user email from API response."); + logger.warn("Could not determine GitHub user email from API response."); } return { userName: finalUserName, userEmail: finalUserEmail }; } catch (e) { - console.error("Error fetching GitHub user info:", e); + logger.error(`Error fetching GitHub user info`, { + ...(e instanceof Error && { + name: e.name, + message: e.message, + stack: e.stack, + }), + }); return {}; } } @@ -197,10 +227,13 @@ export async function configureGitUserInRepo( needsGitConfig = true; } } catch (checkError) { - console.warn( - "Could not check existing git config, will attempt to set it:", - checkError, - ); + logger.warn(`Could not check existing git config, will attempt to set it`, { + ...(checkError instanceof Error && { + name: checkError.name, + message: checkError.message, + stack: checkError.stack, + }), + }); needsGitConfig = true; } @@ -214,14 +247,11 @@ export async function configureGitUserInRepo( ); await sandbox.setTimeout(TIMEOUT_MS); if (configUserNameOutput.exitCode !== 0) { - console.error( - "Failed to set git user.name:", - configUserNameOutput.stderr || configUserNameOutput.stdout, - ); - } else { - console.log("\nSet git user.name successfully.", { - userName, + logger.error(`Failed to set git user.name`, { + configUserNameOutput, }); + } else { + logger.info(`Set git user.name to '${userName}' successfully.`); } } @@ -232,18 +262,15 @@ export async function configureGitUserInRepo( ); await sandbox.setTimeout(TIMEOUT_MS); if (configUserEmailOutput.exitCode !== 0) { - console.error( - "Failed to set git user.email:", - configUserEmailOutput.stderr || configUserEmailOutput.stdout, - ); - } else { - console.log("\nSet git user.email successfully.", { - userEmail, + logger.error(`Failed to set git user.email`, { + configUserEmailOutput, }); + } else { + logger.info(`Set git user.email to '${userEmail}' successfully.`); } } } else { - console.log( + logger.info( "Git user.name and user.email are already configured in this repository.", ); } @@ -262,14 +289,21 @@ export async function commitAll( await sandbox.setTimeout(TIMEOUT_MS); if (gitAddOutput.exitCode !== 0) { - console.error( - "Failed to commit all changes to git repository", + logger.error(`Failed to commit all changes to git repository`, { gitAddOutput, - ); + }); } return gitAddOutput; } catch (e) { - console.error("Failed to commit all changes to git repository", e); + const errorFields = getSandboxErrorFields(e); + logger.error(`Failed to commit all changes to git repository`, { + ...(errorFields && { errorFields }), + ...(e instanceof Error && { + name: e.name, + message: e.message, + stack: e.stack, + }), + }); return false; } } @@ -295,13 +329,23 @@ export async function commitAllAndPush( await sandbox.setTimeout(TIMEOUT_MS); if (gitPushOutput.exitCode !== 0) { - console.error("Failed to push changes to git repository", gitPushOutput); + logger.error(`Failed to push changes to git repository`, { + gitPushOutput, + }); return false; } return gitPushOutput; } catch (e) { - console.error("Failed to commit all and push changes to git repository", e); + const errorFields = getSandboxErrorFields(e); + logger.error(`Failed to commit all and push changes to git repository`, { + ...(errorFields && { errorFields }), + ...(e instanceof Error && { + name: e.name, + message: e.message, + stack: e.stack, + }), + }); return false; } } @@ -315,7 +359,9 @@ export async function getChangedFilesStatus( }); if (gitStatusOutput.exitCode !== 0) { - console.error("Failed to get changed files status", gitStatusOutput); + logger.error(`Failed to get changed files status`, { + gitStatusOutput, + }); return []; } @@ -332,15 +378,15 @@ export async function checkoutBranchAndCommit( branchName?: string; }, ): Promise { - console.log("\nChecking out branch and committing changes..."); + logger.info("Checking out branch and committing changes..."); const absoluteRepoDir = getRepoAbsolutePath(config); const branchName = options?.branchName || getBranchName(config); await checkoutBranch(absoluteRepoDir, branchName, sandbox); - console.log(`Committing changes to branch ${branchName}`); + logger.info(`Committing changes to branch ${branchName}`); await commitAllAndPush(absoluteRepoDir, "Apply patch", sandbox); - console.log("Successfully checked out & committed changes.\n"); + logger.info("Successfully checked out & committed changes."); return branchName; } diff --git a/src/utils/load-model.ts b/src/utils/load-model.ts index 4d8e7641..0d1a6719 100644 --- a/src/utils/load-model.ts +++ b/src/utils/load-model.ts @@ -39,11 +39,9 @@ export async function loadModel(config: GraphConfig, task: Task) { let thinkingModel = false; if (modelNameParts[0] === "extended-thinking") { - console.log("THINKING ENABLED", modelNameParts); // Using a thinking model. Remove it from the model name. modelNameParts.shift(); thinkingModel = true; - console.log("THINKING ENABLED", modelNameParts); } const modelName = modelNameParts.join(":"); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 00000000..6ff22ad9 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,94 @@ +export enum LogLevel { + DEBUG = "debug", + INFO = "info", + WARN = "warn", + ERROR = "error", +} + +// ANSI escape codes +const RESET = "\x1b[0m"; +const BOLD = "\x1b[1m"; + +// Define a list of colors (foreground) +const COLORS = [ + "\x1b[31m", // Red + "\x1b[32m", // Green + "\x1b[33m", // Yellow + "\x1b[34m", // Blue + "\x1b[35m", // Magenta + "\x1b[36m", // Cyan + "\x1b[91m", // Bright Red + "\x1b[92m", // Bright Green + "\x1b[93m", // Bright Yellow + "\x1b[94m", // Bright Blue + "\x1b[95m", // Bright Magenta + "\x1b[96m", // Bright Cyan +]; + +// Simple hashing function to get a positive integer +function simpleHash(str: string): number { + let hash = 0; + if (str.length === 0) { + return hash; + } + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash |= 0; // Convert to 32bit integer + } + return Math.abs(hash); // Ensure positive for modulo index +} + +export function createLogger(level: LogLevel, prefix: string) { + const hash = simpleHash(prefix); + const color = COLORS[hash % COLORS.length]; + const styledPrefix = `${BOLD}${color}[${prefix}]${RESET}`; // Apply bold and color + + return { + debug: (message: string, data?: any) => { + if (level === LogLevel.DEBUG) { + if (data !== undefined) { + console.log(`${styledPrefix} ${message}`, data); + } else { + console.log(`${styledPrefix} ${message}`); + } + } + }, + info: (message: string, data?: any) => { + if (level === LogLevel.INFO || level === LogLevel.DEBUG) { + if (data !== undefined) { + console.log(`${styledPrefix} ${message}`, data); + } else { + console.log(`${styledPrefix} ${message}`); + } + } + }, + warn: (message: string, data?: any) => { + if ( + level === LogLevel.WARN || + level === LogLevel.INFO || + level === LogLevel.DEBUG + ) { + if (data !== undefined) { + console.log(`${styledPrefix} ${message}`, data); + } else { + console.log(`${styledPrefix} ${message}`); + } + } + }, + error: (message: string, data?: any) => { + if ( + level === LogLevel.ERROR || + level === LogLevel.WARN || + level === LogLevel.INFO || + level === LogLevel.DEBUG + ) { + if (data !== undefined) { + console.log(`${styledPrefix} ${message}`, data); + } else { + console.log(`${styledPrefix} ${message}`); + } + } + }, + }; +} diff --git a/src/utils/read-write.ts b/src/utils/read-write.ts index 8e085204..5613bca5 100644 --- a/src/utils/read-write.ts +++ b/src/utils/read-write.ts @@ -1,7 +1,10 @@ import { Sandbox } from "@e2b/code-interpreter"; +import { createLogger, LogLevel } from "./logger.js"; import { TIMEOUT_MS } from "../constants.js"; import { getSandboxErrorFields } from "./sandbox-error-fields.js"; +const logger = createLogger(LogLevel.INFO, "ReadWriteUtil"); + export async function readFile( sandbox: Sandbox, filePath: string, @@ -15,18 +18,17 @@ export async function readFile( await sandbox.setTimeout(TIMEOUT_MS); if (readOutput.exitCode !== 0) { - console.error( - `\nError reading file '${filePath}' from sandbox via cat:`, + logger.error(`Error reading file '${filePath}' from sandbox via cat:`, { readOutput, - ); + }); return { success: false, output: `FAILED TO READ FILE from sandbox '${filePath}'. Exit code: ${readOutput.exitCode}.\nStderr: ${readOutput.stderr}.\nStdout: ${readOutput.stdout}`, }; } if (readOutput.stderr) { - console.warn( - `\nStderr while reading file '${filePath}' from sandbox via cat: ${readOutput.stderr}`, + logger.warn( + `Stderr while reading file '${filePath}' from sandbox via cat: ${readOutput.stderr}`, ); } return { @@ -34,9 +36,13 @@ export async function readFile( output: readOutput.stdout, }; } catch (e: any) { - console.error( - `\nException while trying to read file '${filePath}' from sandbox via cat:`, - e, + logger.error( + `Exception while trying to read file '${filePath}' from sandbox via cat:`, + { + ...(e instanceof Error + ? { name: e.name, message: e.message, stack: e.stack } + : { error: e }), + }, ); let outputMessage = `FAILED TO EXECUTE READ COMMAND for sandbox '${filePath}'.`; const errorFields = getSandboxErrorFields(e); @@ -71,18 +77,17 @@ ${delimiter}`; await sandbox.setTimeout(TIMEOUT_MS); if (writeOutput.exitCode !== 0) { - console.error( - `\nError writing file '${filePath}' to sandbox via printf:`, + logger.error(`Error writing file '${filePath}' to sandbox via printf:`, { writeOutput, - ); + }); return { success: false, output: `FAILED TO WRITE FILE to sandbox '${filePath}'. Exit code: ${writeOutput.exitCode}. Stderr: ${writeOutput.stderr}. Stdout: ${writeOutput.stdout}`, }; } if (writeOutput.stderr) { - console.warn( - `\nStderr while writing file '${filePath}' to sandbox via printf: ${writeOutput.stderr}`, + logger.warn( + `Stderr while writing file '${filePath}' to sandbox via printf: ${writeOutput.stderr}`, ); } return { @@ -90,9 +95,13 @@ ${delimiter}`; output: `Successfully wrote file '${filePath}' to sandbox via printf.`, }; } catch (e: any) { - console.error( - `\nException while trying to write file '${filePath}' to sandbox via printf:`, - e, + logger.error( + `Exception while trying to write file '${filePath}' to sandbox via printf:`, + { + ...(e instanceof Error + ? { name: e.name, message: e.message, stack: e.stack } + : { error: e }), + }, ); let outputMessage = `FAILED TO EXECUTE WRITE COMMAND for sandbox '${filePath}'.`;