fix: Implement logger (#17)

* fix: Docs and scripts

* cr

* fix: better err handling

* cr

* cr

* fix writing

* fix types

* fix: Implement logger
This commit is contained in:
Brace Sproul 2025-05-24 14:38:14 -07:00 • committed by GitHub
parent 04b14cae32
commit 20c483f327
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 299 additions and 109 deletions

View file

@ -3,6 +3,9 @@ import { loadModel, Task } from "../utils/load-model.js";
import { shellTool, applyPatchTool } from "../tools/index.js"; import { shellTool, applyPatchTool } from "../tools/index.js";
import { formatPlanPrompt } from "../utils/plan-prompt.js"; import { formatPlanPrompt } from "../utils/plan-prompt.js";
import { pauseSandbox } from "../utils/sandbox.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. 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. // No tool calls means the graph is going to end. Pause the sandbox.
let newSandboxSessionId: string | undefined; let newSandboxSessionId: string | undefined;
if (!hasToolCalls && state.sandboxSessionId) { 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); newSandboxSessionId = await pauseSandbox(state.sandboxSessionId);
} }
logger.info("Generated action", {
name: response.tool_calls?.[0].name,
args: response.tool_calls?.[0].args,
});
return { return {
messages: [response], messages: [response],
...(newSandboxSessionId && { sandboxSessionId: newSandboxSessionId }), ...(newSandboxSessionId && { sandboxSessionId: newSandboxSessionId }),

View file

@ -1,4 +1,5 @@
import { Sandbox } from "@e2b/code-interpreter"; import { Sandbox } from "@e2b/code-interpreter";
import { createLogger, LogLevel } from "../utils/logger.js";
import { import {
GraphState, GraphState,
GraphConfig, GraphConfig,
@ -14,6 +15,8 @@ import {
} from "../utils/git/index.js"; } from "../utils/git/index.js";
import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js"; import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js";
const logger = createLogger(LogLevel.INFO, "Initialize");
const SANDBOX_TEMPLATE_ID = "eh0860emqx28qyxmbctu"; const SANDBOX_TEMPLATE_ID = "eh0860emqx28qyxmbctu";
async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) { async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) {
@ -32,7 +35,7 @@ async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) {
gitCloneCommand.push(repoUrlWithToken); gitCloneCommand.push(repoUrlWithToken);
} }
console.log("Cloning repository...", { logger.info("Cloning repository", {
command: gitCloneCommand.join(" "), command: gitCloneCommand.join(" "),
}); });
return await sandbox.commands.run( return await sandbox.commands.run(
@ -41,7 +44,7 @@ async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) {
); );
} catch (e) { } catch (e) {
const errorFields = getSandboxErrorFields(e); const errorFields = getSandboxErrorFields(e);
console.error("Failed to clone repository", errorFields ?? e); logger.error("Failed to clone repository", errorFields ?? e);
throw e; throw e;
} }
} }
@ -63,7 +66,7 @@ export async function initialize(
if (sandboxSessionId) { if (sandboxSessionId) {
try { try {
console.log("Sandbox session ID exists. Resuming...", { logger.info("Sandbox session ID exists. Resuming", {
sandboxSessionId, sandboxSessionId,
}); });
// Resume the sandbox if the session ID is in the config. // Resume the sandbox if the session ID is in the config.
@ -76,7 +79,7 @@ export async function initialize(
}; };
} catch (e) { } catch (e) {
// Error thrown, log it and continue. Will create a new sandbox session since the resumption failed. // 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( const sandbox = await Sandbox.create(
SANDBOX_TEMPLATE_ID, SANDBOX_TEMPLATE_ID,
TIMEOUT_EXTENSION_OPT, TIMEOUT_EXTENSION_OPT,
@ -97,16 +100,16 @@ export async function initialize(
const res = await cloneRepo(sandbox, target_repository); const res = await cloneRepo(sandbox, target_repository);
if (res.error) { if (res.error) {
// TODO: This should probably be an interrupt. // 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}`); throw new Error(`Failed to clone repository.\n${res.error}`);
} }
console.log("Repository cloned successfully."); logger.info("Repository cloned successfully.");
const absoluteRepoDir = getRepoAbsolutePath(config); 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); await configureGitUserInRepo(absoluteRepoDir, sandbox);
console.log("Git user configured successfully."); logger.info("Git user configured successfully.");
const checkoutBranchRes = await checkoutBranch( const checkoutBranchRes = await checkoutBranch(
absoluteRepoDir, absoluteRepoDir,
@ -116,7 +119,7 @@ export async function initialize(
if (!checkoutBranchRes) { if (!checkoutBranchRes) {
// TODO: This should probably be an interrupt. // 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"); throw new Error("Failed to checkout branch");
} }

View file

@ -1,8 +1,11 @@
import { z } from "zod"; import { z } from "zod";
import { createLogger, LogLevel } from "../utils/logger.js";
import { GraphConfig, GraphState, GraphUpdate, PlanItem } from "../types.js"; import { GraphConfig, GraphState, GraphUpdate, PlanItem } from "../types.js";
import { loadModel, Task } from "../utils/load-model.js"; import { loadModel, Task } from "../utils/load-model.js";
import { formatPlanPrompt } from "../utils/plan-prompt.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. 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. 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); const remainingTask = state.plan.find((p) => !p.completed);
if (!remainingTask) { if (!remainingTask) {
// No remaining tasks, end the process // No remaining tasks, end the process
console.log( logger.info(
"Found no remaining tasks in the plan during the check plan step.", "Found no remaining tasks in the plan during the check plan step.",
); );
return {}; return {};

View file

@ -1,4 +1,5 @@
import { isAIMessage, ToolMessage } from "@langchain/core/messages"; import { isAIMessage, ToolMessage } from "@langchain/core/messages";
import { createLogger, LogLevel } from "../utils/logger.js";
import { applyPatchTool, shellTool } from "../tools/index.js"; import { applyPatchTool, shellTool } from "../tools/index.js";
import { GraphState, GraphConfig, GraphUpdate } from "../types.js"; import { GraphState, GraphConfig, GraphUpdate } from "../types.js";
import { import {
@ -8,6 +9,8 @@ import {
} from "../utils/git/index.js"; } from "../utils/git/index.js";
import { Sandbox } from "@e2b/code-interpreter"; import { Sandbox } from "@e2b/code-interpreter";
const logger = createLogger(LogLevel.INFO, "TakeAction");
export async function takeAction( export async function takeAction(
state: GraphState, state: GraphState,
config: GraphConfig, config: GraphConfig,
@ -45,7 +48,11 @@ export async function takeAction(
// @ts-expect-error tool.invoke types are weird here... // @ts-expect-error tool.invoke types are weird here...
result = await tool.invoke(toolCall.args); result = await tool.invoke(toolCall.args);
} catch (e) { } 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"; const errMessage = e instanceof Error ? e.message : "Unknown error";
result = `FAILED TO CALL TOOL: "${toolCall.name}"\n\nError: ${errMessage}`; result = `FAILED TO CALL TOOL: "${toolCall.name}"\n\nError: ${errMessage}`;
} }
@ -66,7 +73,7 @@ export async function takeAction(
let branchName: string | undefined = state.branchName; let branchName: string | undefined = state.branchName;
if (changedFiles.length > 0) { if (changedFiles.length > 0) {
console.log(`\nHas ${changedFiles.length} changed files. Committing...`, { logger.info(`Has ${changedFiles.length} changed files. Committing.`, {
changedFiles, changedFiles,
}); });
branchName = await checkoutBranchAndCommit(config, sandbox, { branchName = await checkoutBranchAndCommit(config, sandbox, {

View file

@ -6,6 +6,9 @@ import { Sandbox } from "@e2b/code-interpreter";
import { readFile, writeFile } from "../utils/read-write.js"; import { readFile, writeFile } from "../utils/read-write.js";
import { getCurrentTaskInput } from "@langchain/langgraph"; import { getCurrentTaskInput } from "@langchain/langgraph";
import { fixGitPatch } from "../utils/diff.js"; import { fixGitPatch } from "../utils/diff.js";
import { createLogger, LogLevel } from "../utils/logger.js";
const logger = createLogger(LogLevel.INFO, "ApplyPatchTool");
const applyPatchToolSchema = z.object({ const applyPatchToolSchema = z.object({
diff: z.string().describe("The diff to apply. Use a standard diff format."), diff: z.string().describe("The diff to apply. Use a standard diff format."),
@ -17,7 +20,7 @@ export const applyPatchTool = tool(
const state = getCurrentTaskInput<GraphState>(); const state = getCurrentTaskInput<GraphState>();
const { sandboxSessionId } = state; const { sandboxSessionId } = state;
if (!sandboxSessionId) { 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, input,
}); });
throw new Error("FAILED TO RUN COMMAND: No sandbox session ID provided"); throw new Error("FAILED TO RUN COMMAND: No sandbox session ID provided");
@ -32,37 +35,46 @@ export const applyPatchTool = tool(
file_path, file_path,
); );
if (!readFileSuccess) { if (!readFileSuccess) {
logger.error("Failed to read file", readFileOutput);
return 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; let patchedContent: string | false;
try { try {
logger.info(`Applying patch to file ${file_path}`);
const fixedDiff = fixGitPatch(diff, { const fixedDiff = fixGitPatch(diff, {
[file_path]: readFileOutput, [file_path]: readFileOutput,
}); });
console.log("\n\nfixedDiff\n\n", fixedDiff);
patchedContent = applyPatch(readFileOutput, fixedDiff); patchedContent = applyPatch(readFileOutput, fixedDiff);
} catch (e) { } 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"; 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}`; return `FAILED TO APPLY PATCH: The diff could not be applied to file '${file_path}'.\n\nError: ${errMessage}`;
} }
if (patchedContent === false) { 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)}...`; 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 } = const { success: writeFileSuccess, output: writeFileOutput } =
await writeFile(sandbox, file_path, patchedContent); await writeFile(sandbox, file_path, patchedContent);
if (!writeFileSuccess) { if (!writeFileSuccess) {
logger.error("Failed to write file", {
writeFileOutput,
});
return writeFileOutput; return writeFileOutput;
} }
logger.info(
`Successfully applied diff to \`${file_path}\` and saved changes.`,
);
return `Successfully applied diff to \`${file_path}\` and saved changes.`; return `Successfully applied diff to \`${file_path}\` and saved changes.`;
}, },
{ {

View file

@ -4,8 +4,9 @@ import { Sandbox } from "@e2b/code-interpreter";
import { GraphState } from "../types.js"; import { GraphState } from "../types.js";
import { getCurrentTaskInput } from "@langchain/langgraph"; import { getCurrentTaskInput } from "@langchain/langgraph";
import { TIMEOUT_MS } from "../constants.js"; 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({ const shellToolSchema = z.object({
command: z.array(z.string()).describe("The command to run"), command: z.array(z.string()).describe("The command to run"),
@ -55,26 +56,28 @@ export const shellTool = tool(
} }
return result.stdout; return result.stdout;
} catch (e: any) { } catch (e) {
if (typeof e === "object" && "result" in e && e.result) { const errorFields = getSandboxErrorFields(e);
if (errorFields) {
console.error("Failed to run command", { console.error("Failed to run command", {
error: e.message,
error_result: e.result,
input, input,
error: errorFields,
}); });
return ( return `Command failed. Exit code: ${errorFields.exitCode}\nError: ${errorFields.error}\nStderr:\n${errorFields.stderr}\nStdout:\n${errorFields.stdout}`;
"FAILED TO RUN COMMAND: " +
e.message +
"\n" +
JSON.stringify(e.result, null, 2)
);
} }
console.error("Failed to run command: " + e.message, { console.error(
error: e, "Failed to run command: " +
input, (e instanceof Error ? e.message : "Unknown error"),
}); {
throw new Error("FAILED TO RUN COMMAND: " + e.message); error: e,
input,
},
);
throw new Error(
"FAILED TO RUN COMMAND: " +
(e instanceof Error ? e.message : "Unknown error"),
);
} }
}, },
{ {

View file

@ -1,3 +1,7 @@
import { createLogger, LogLevel } from "./logger.js";
const logger = createLogger(LogLevel.INFO, "DiffUtil");
interface Hunk { interface Hunk {
oldStart: number; oldStart: number;
oldLines: number; oldLines: number;
@ -386,8 +390,12 @@ export function fixGitPatch(
} }
return result; return result;
} catch (error: any) { } catch (e) {
console.error("Error fixing patch:", error); logger.error(`Error fixing patch:`, {
...(e instanceof Error
? { name: e.name, message: e.message, stack: e.stack }
: { error: e }),
});
return patchString; return patchString;
} }
} }

View file

@ -1,8 +1,11 @@
import { CommandResult, Sandbox } from "@e2b/code-interpreter"; import { CommandResult, Sandbox } from "@e2b/code-interpreter";
import { createLogger, LogLevel } from "../logger.js";
import { GraphConfig } from "../../types.js"; import { GraphConfig } from "../../types.js";
import { TIMEOUT_MS } from "../../constants.js"; import { TIMEOUT_MS } from "../../constants.js";
import { getSandboxErrorFields } from "../sandbox-error-fields.js"; import { getSandboxErrorFields } from "../sandbox-error-fields.js";
const logger = createLogger(LogLevel.INFO, "GitUtil");
export function getRepoAbsolutePath(config: GraphConfig): string { export function getRepoAbsolutePath(config: GraphConfig): string {
const repoName = config.configurable?.target_repository.repo; const repoName = config.configurable?.target_repository.repo;
if (!repoName) { if (!repoName) {
@ -26,9 +29,7 @@ export async function checkoutBranch(
branchName: string, branchName: string,
sandbox: Sandbox, sandbox: Sandbox,
): Promise<CommandResult | false> { ): Promise<CommandResult | false> {
console.log("\nChecking out branch...", { logger.info(`Checking out branch '${branchName}'...`);
branchName,
});
try { try {
const getCurrentBranchOutput = await sandbox.commands.run( const getCurrentBranchOutput = await sandbox.commands.run(
@ -38,11 +39,13 @@ export async function checkoutBranch(
await sandbox.setTimeout(TIMEOUT_MS); await sandbox.setTimeout(TIMEOUT_MS);
if (getCurrentBranchOutput.exitCode !== 0) { if (getCurrentBranchOutput.exitCode !== 0) {
console.error("Failed to get current branch", getCurrentBranchOutput); logger.error(`Failed to get current branch`, {
getCurrentBranchOutput,
});
} else { } else {
const currentBranch = getCurrentBranchOutput.stdout.trim(); const currentBranch = getCurrentBranchOutput.stdout.trim();
if (currentBranch === branchName) { if (currentBranch === branchName) {
console.log(`\nAlready on branch '${branchName}'. No checkout needed.`); logger.info(`Already on branch '${branchName}'. No checkout needed.`);
return { return {
stdout: `Already on branch ${branchName}`, stdout: `Already on branch ${branchName}`,
stderr: "", stderr: "",
@ -52,15 +55,22 @@ export async function checkoutBranch(
} }
} catch (e) { } catch (e) {
const errorFields = getSandboxErrorFields(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; return false;
} }
let checkoutCommand: string; let checkoutCommand: string;
try { try {
console.log("\nChecking if branch exists...", { logger.info(
command: `git rev-parse --verify --quiet "refs/heads/${branchName}"`, `Checking if branch 'refs/heads/${branchName}' exists using 'git rev-parse --verify --quiet'`,
}); );
// Check if branch exists using git rev-parse for robustness // Check if branch exists using git rev-parse for robustness
const checkBranchExistsOutput = await sandbox.commands.run( const checkBranchExistsOutput = await sandbox.commands.run(
`git rev-parse --verify --quiet "refs/heads/${branchName}"`, `git rev-parse --verify --quiet "refs/heads/${branchName}"`,
@ -85,7 +95,13 @@ export async function checkoutBranch(
) { ) {
checkoutCommand = `git checkout -b "${branchName}"`; checkoutCommand = `git checkout -b "${branchName}"`;
} else { } 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; return false;
} }
} }
@ -96,19 +112,27 @@ export async function checkoutBranch(
}); });
if (gitCheckoutOutput.exitCode !== 0) { if (gitCheckoutOutput.exitCode !== 0) {
console.error("\nFailed to checkout branch", gitCheckoutOutput); logger.error(`Failed to checkout branch`, {
gitCheckoutOutput,
});
return false; return false;
} }
console.log("\nChecked out branch successfully.", { logger.info(`Checked out branch '${branchName}' successfully.`, {
branchName, gitCheckoutOutput,
gitCheckoutOutput: gitCheckoutOutput.stdout,
}); });
return gitCheckoutOutput; return gitCheckoutOutput;
} catch (e) { } catch (e) {
const errorFields = getSandboxErrorFields(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; return false;
} }
} }
@ -126,7 +150,7 @@ async function getGitUserDetailsFromGitHub(): Promise<{
}> { }> {
const githubToken = process.env.GITHUB_PAT; const githubToken = process.env.GITHUB_PAT;
if (!githubToken) { if (!githubToken) {
console.warn( logger.warn(
"GITHUB_PAT environment variable is not set. Cannot fetch user details from GitHub.", "GITHUB_PAT environment variable is not set. Cannot fetch user details from GitHub.",
); );
return {}; return {};
@ -141,9 +165,9 @@ async function getGitUserDetailsFromGitHub(): Promise<{
}); });
if (!response.ok) { if (!response.ok) {
console.error( logger.error(`Failed to fetch GitHub user info`, {
`Failed to fetch GitHub user info: ${response.status} ${response.statusText}. Response: ${await response.text()}`, response,
); });
return {}; return {};
} }
@ -161,14 +185,20 @@ async function getGitUserDetailsFromGitHub(): Promise<{
const finalUserEmail = fetchedUserEmail || undefined; const finalUserEmail = fetchedUserEmail || undefined;
if (!finalUserName) { if (!finalUserName) {
console.warn("Could not determine GitHub username from API response."); logger.warn("Could not determine GitHub username from API response.");
} }
if (!finalUserEmail) { 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 }; return { userName: finalUserName, userEmail: finalUserEmail };
} catch (e) { } 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 {}; return {};
} }
} }
@ -197,10 +227,13 @@ export async function configureGitUserInRepo(
needsGitConfig = true; needsGitConfig = true;
} }
} catch (checkError) { } catch (checkError) {
console.warn( logger.warn(`Could not check existing git config, will attempt to set it`, {
"Could not check existing git config, will attempt to set it:", ...(checkError instanceof Error && {
checkError, name: checkError.name,
); message: checkError.message,
stack: checkError.stack,
}),
});
needsGitConfig = true; needsGitConfig = true;
} }
@ -214,14 +247,11 @@ export async function configureGitUserInRepo(
); );
await sandbox.setTimeout(TIMEOUT_MS); await sandbox.setTimeout(TIMEOUT_MS);
if (configUserNameOutput.exitCode !== 0) { if (configUserNameOutput.exitCode !== 0) {
console.error( logger.error(`Failed to set git user.name`, {
"Failed to set git user.name:", configUserNameOutput,
configUserNameOutput.stderr || configUserNameOutput.stdout,
);
} else {
console.log("\nSet git user.name successfully.", {
userName,
}); });
} else {
logger.info(`Set git user.name to '${userName}' successfully.`);
} }
} }
@ -232,18 +262,15 @@ export async function configureGitUserInRepo(
); );
await sandbox.setTimeout(TIMEOUT_MS); await sandbox.setTimeout(TIMEOUT_MS);
if (configUserEmailOutput.exitCode !== 0) { if (configUserEmailOutput.exitCode !== 0) {
console.error( logger.error(`Failed to set git user.email`, {
"Failed to set git user.email:", configUserEmailOutput,
configUserEmailOutput.stderr || configUserEmailOutput.stdout,
);
} else {
console.log("\nSet git user.email successfully.", {
userEmail,
}); });
} else {
logger.info(`Set git user.email to '${userEmail}' successfully.`);
} }
} }
} else { } else {
console.log( logger.info(
"Git user.name and user.email are already configured in this repository.", "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); await sandbox.setTimeout(TIMEOUT_MS);
if (gitAddOutput.exitCode !== 0) { if (gitAddOutput.exitCode !== 0) {
console.error( logger.error(`Failed to commit all changes to git repository`, {
"Failed to commit all changes to git repository",
gitAddOutput, gitAddOutput,
); });
} }
return gitAddOutput; return gitAddOutput;
} catch (e) { } 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; return false;
} }
} }
@ -295,13 +329,23 @@ export async function commitAllAndPush(
await sandbox.setTimeout(TIMEOUT_MS); await sandbox.setTimeout(TIMEOUT_MS);
if (gitPushOutput.exitCode !== 0) { 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 false;
} }
return gitPushOutput; return gitPushOutput;
} catch (e) { } 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; return false;
} }
} }
@ -315,7 +359,9 @@ export async function getChangedFilesStatus(
}); });
if (gitStatusOutput.exitCode !== 0) { if (gitStatusOutput.exitCode !== 0) {
console.error("Failed to get changed files status", gitStatusOutput); logger.error(`Failed to get changed files status`, {
gitStatusOutput,
});
return []; return [];
} }
@ -332,15 +378,15 @@ export async function checkoutBranchAndCommit(
branchName?: string; branchName?: string;
}, },
): Promise<string> { ): Promise<string> {
console.log("\nChecking out branch and committing changes..."); logger.info("Checking out branch and committing changes...");
const absoluteRepoDir = getRepoAbsolutePath(config); const absoluteRepoDir = getRepoAbsolutePath(config);
const branchName = options?.branchName || getBranchName(config); const branchName = options?.branchName || getBranchName(config);
await checkoutBranch(absoluteRepoDir, branchName, sandbox); 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); await commitAllAndPush(absoluteRepoDir, "Apply patch", sandbox);
console.log("Successfully checked out & committed changes.\n"); logger.info("Successfully checked out & committed changes.");
return branchName; return branchName;
} }

View file

@ -39,11 +39,9 @@ export async function loadModel(config: GraphConfig, task: Task) {
let thinkingModel = false; let thinkingModel = false;
if (modelNameParts[0] === "extended-thinking") { if (modelNameParts[0] === "extended-thinking") {
console.log("THINKING ENABLED", modelNameParts);
// Using a thinking model. Remove it from the model name. // Using a thinking model. Remove it from the model name.
modelNameParts.shift(); modelNameParts.shift();
thinkingModel = true; thinkingModel = true;
console.log("THINKING ENABLED", modelNameParts);
} }
const modelName = modelNameParts.join(":"); const modelName = modelNameParts.join(":");

94
src/utils/logger.ts Normal file
View file

@ -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}`);
}
}
},
};
}

View file

@ -1,7 +1,10 @@
import { Sandbox } from "@e2b/code-interpreter"; import { Sandbox } from "@e2b/code-interpreter";
import { createLogger, LogLevel } from "./logger.js";
import { TIMEOUT_MS } from "../constants.js"; import { TIMEOUT_MS } from "../constants.js";
import { getSandboxErrorFields } from "./sandbox-error-fields.js"; import { getSandboxErrorFields } from "./sandbox-error-fields.js";
const logger = createLogger(LogLevel.INFO, "ReadWriteUtil");
export async function readFile( export async function readFile(
sandbox: Sandbox, sandbox: Sandbox,
filePath: string, filePath: string,
@ -15,18 +18,17 @@ export async function readFile(
await sandbox.setTimeout(TIMEOUT_MS); await sandbox.setTimeout(TIMEOUT_MS);
if (readOutput.exitCode !== 0) { if (readOutput.exitCode !== 0) {
console.error( logger.error(`Error reading file '${filePath}' from sandbox via cat:`, {
`\nError reading file '${filePath}' from sandbox via cat:`,
readOutput, readOutput,
); });
return { return {
success: false, success: false,
output: `FAILED TO READ FILE from sandbox '${filePath}'. Exit code: ${readOutput.exitCode}.\nStderr: ${readOutput.stderr}.\nStdout: ${readOutput.stdout}`, output: `FAILED TO READ FILE from sandbox '${filePath}'. Exit code: ${readOutput.exitCode}.\nStderr: ${readOutput.stderr}.\nStdout: ${readOutput.stdout}`,
}; };
} }
if (readOutput.stderr) { if (readOutput.stderr) {
console.warn( logger.warn(
`\nStderr while reading file '${filePath}' from sandbox via cat: ${readOutput.stderr}`, `Stderr while reading file '${filePath}' from sandbox via cat: ${readOutput.stderr}`,
); );
} }
return { return {
@ -34,9 +36,13 @@ export async function readFile(
output: readOutput.stdout, output: readOutput.stdout,
}; };
} catch (e: any) { } catch (e: any) {
console.error( logger.error(
`\nException while trying to read file '${filePath}' from sandbox via cat:`, `Exception while trying to read file '${filePath}' from sandbox via cat:`,
e, {
...(e instanceof Error
? { name: e.name, message: e.message, stack: e.stack }
: { error: e }),
},
); );
let outputMessage = `FAILED TO EXECUTE READ COMMAND for sandbox '${filePath}'.`; let outputMessage = `FAILED TO EXECUTE READ COMMAND for sandbox '${filePath}'.`;
const errorFields = getSandboxErrorFields(e); const errorFields = getSandboxErrorFields(e);
@ -71,18 +77,17 @@ ${delimiter}`;
await sandbox.setTimeout(TIMEOUT_MS); await sandbox.setTimeout(TIMEOUT_MS);
if (writeOutput.exitCode !== 0) { if (writeOutput.exitCode !== 0) {
console.error( logger.error(`Error writing file '${filePath}' to sandbox via printf:`, {
`\nError writing file '${filePath}' to sandbox via printf:`,
writeOutput, writeOutput,
); });
return { return {
success: false, success: false,
output: `FAILED TO WRITE FILE to sandbox '${filePath}'. Exit code: ${writeOutput.exitCode}. Stderr: ${writeOutput.stderr}. Stdout: ${writeOutput.stdout}`, output: `FAILED TO WRITE FILE to sandbox '${filePath}'. Exit code: ${writeOutput.exitCode}. Stderr: ${writeOutput.stderr}. Stdout: ${writeOutput.stdout}`,
}; };
} }
if (writeOutput.stderr) { if (writeOutput.stderr) {
console.warn( logger.warn(
`\nStderr while writing file '${filePath}' to sandbox via printf: ${writeOutput.stderr}`, `Stderr while writing file '${filePath}' to sandbox via printf: ${writeOutput.stderr}`,
); );
} }
return { return {
@ -90,9 +95,13 @@ ${delimiter}`;
output: `Successfully wrote file '${filePath}' to sandbox via printf.`, output: `Successfully wrote file '${filePath}' to sandbox via printf.`,
}; };
} catch (e: any) { } catch (e: any) {
console.error( logger.error(
`\nException while trying to write file '${filePath}' to sandbox via printf:`, `Exception while trying to write file '${filePath}' to sandbox via printf:`,
e, {
...(e instanceof Error
? { name: e.name, message: e.message, stack: e.stack }
: { error: e }),
},
); );
let outputMessage = `FAILED TO EXECUTE WRITE COMMAND for sandbox '${filePath}'.`; let outputMessage = `FAILED TO EXECUTE WRITE COMMAND for sandbox '${filePath}'.`;