From fd0d50be26a5115d27d84127b972a5d82a280fbb Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 23 May 2025 16:43:14 -0700 Subject: [PATCH] fix: Docs and scripts (#15) * fix: Docs and scripts * cr * fix: better err handling * cr --- .env.example | 24 +++++----- README.md | 89 ++++++++++++++++++++++++++++++++++- langgraph.json | 2 +- package.json | 6 +-- scripts/run-e2e.ts | 14 +++--- scripts/run-from-plan.ts | 2 +- src/nodes/generate-message.ts | 1 + src/nodes/initialize.ts | 35 ++++++++------ src/tools/apply-patch.ts | 9 +++- 9 files changed, 144 insertions(+), 38 deletions(-) diff --git a/.env.example b/.env.example index b7512da9..1a045132 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,16 @@ -# Required +# ------------------LangSmith tracing------------------ +LANGCHAIN_PROJECT="default" +LANGCHAIN_API_KEY="" +LANGCHAIN_TRACING_V2=true +# ----------------------------------------------------- + +# Defaults to Anthropic models, OpenAI & Google keys are optional, unless using those models +ANTHROPIC_API_KEY="" OPENAI_API_KEY="" -# GOOGLE_API_KEY="" -# ANTHROPIC_API_KEY="" +GOOGLE_API_KEY="" -MCP_URL="" +# E2B API key for accessing and modifying the code in the cloud sandbox. +E2B_API_KEY="" -# Optional, but recommended for best in class tracing and observability. -# LANGSMITH_PROJECT="default" -# LANGSMITH_API_KEY="" -# LANGSMITH_TRACING_V2=true - -# Optional -# FINANCIAL_DATASETS_API_KEY="" \ No newline at end of file +# Your GitHub PAT with access to the repositories you want to modify. +GITHUB_PAT="" diff --git a/README.md b/README.md index 6a2c6ebd..d629a259 100644 --- a/README.md +++ b/README.md @@ -1 +1,88 @@ -# OpenCodex +# Open SWE + +Open SWE is an open-source cloud based coding agent. + +## Usage + +First, clone the repository: + +```bash +git clone https://github.com/langchain-ai/open-swe.git +cd open-swe +``` + +Next, install dependencies: + +```bash +yarn install +``` + +Copy the `.env.example` file to `.env` and fill in the values: + +```bash +cp .env.example .env +``` + +```bash +# ------------------LangSmith tracing------------------ +LANGCHAIN_PROJECT="default" +LANGCHAIN_API_KEY="" +LANGCHAIN_TRACING_V2=true +# ----------------------------------------------------- + +# Defaults to Anthropic models, OpenAI & Google keys are optional, unless using those models +ANTHROPIC_API_KEY="" +OPENAI_API_KEY="" +GOOGLE_API_KEY="" + +# E2B API key for accessing and modifying the code in the cloud sandbox. +E2B_API_KEY="" + +# Your GitHub PAT with access to the repositories you want to modify. +GITHUB_PAT="" + +``` + +Your GitHub personal access token should have access to only the repositories you want to modify. It must have the following `Repository` permissions: + +- `Read access to metadata` +- `Read and Write access to code and pull requests` + +You can generate a personal access token in [GitHub settings](https://github.com/settings/personal-access-tokens). + +## Running the graph + +To run the graph, first modify the `e2e.ts` file to set the `userRequest` along with the target repository information. + +The `userRequest` should contain the task description for Open SWE to execute. + +The `target_repository` object should contain the information for the repository Open SWE should clone and make changes to. You're required to set the `owner` and `repo` properties. The `branch` property is optional, and defaults to whatever the base branch of the repository is. + +After setting the `userRequest` and `target_repository`, run the following command on a terminal session: + +```bash +yarn dev +``` + +This will start the LangGraph server running locally. The server will be available at `http://localhost:2024` by default. + +Then, run the following command on a second terminal session: + +```bash +yarn run:e2e +``` + +This will start the graph with the inputs you specified in the file. The graph always runs a planning sub-graph first, and once it's finished planning it will interrupt with the plan. To interact with the interrupt, you should add Open SWE to [Agent Inbox](https://dev.agentinbox.ai). + +Once you've added the graph to the inbox, you can inspect the interrupt with the generated plan. It will allow you to: + +1. Accept the plan as is. +2. Edit, and submit the plan. +3. Respond with natural language to have the plan modified. This step will _not_ rerun the planning subgraph, it will only modify the plan. +4. Reject the plan by clicking the `Ignore` button. + +If you want to accept the plan as is, you should re-invoke the graph from the terminal with the `yarn run:e2e --threadId ` command. Replace `` with the thread ID of the interrupt you want to accept. This will resume the graph, and log the updates to the terminal. You may also resume from Agent Inbox, however this will not show you the outputs of the graph. + +## Accessing Changes + +Open SWE will automatically create a branch whenever you create a new thread with a naming format of `open-swe/`. Every time a file is created, modified, or deleted, the changes will be committed to this branch. You can access the changes in the repository by checking out this branch. diff --git a/langgraph.json b/langgraph.json index c3067c6f..df260b2a 100644 --- a/langgraph.json +++ b/langgraph.json @@ -1,7 +1,7 @@ { "node_version": "20", "graphs": { - "open-codex": "./src/index.ts:graph" + "open-swe": "./src/index.ts:graph" }, "env": ".env", "dependencies": ["."] diff --git a/package.json b/package.json index 74706fee..93973062 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "open-codex", - "homepage": "https://github.com/langchain-ai/open-codex/blob/main/README.md", + "name": "open-swe", + "homepage": "https://github.com/langchain-ai/open-swe/blob/main/README.md", "repository": { "type": "git", - "url": "https://github.com/langchain-ai/open-codex.git" + "url": "https://github.com/langchain-ai/open-swe.git" }, "private": true, "version": "0.0.0", diff --git a/scripts/run-e2e.ts b/scripts/run-e2e.ts index 5df0bdb1..7e4f7d28 100644 --- a/scripts/run-e2e.ts +++ b/scripts/run-e2e.ts @@ -12,20 +12,20 @@ async function runE2E() { const threadId = uuidv4(); - const userRequest = "ADD YOUR REQUEST HERE"; + const userRequest = + "This repo contains the react/next.js code for my persona/portfolio site. It currently has static values set for the number of stars on the repositories I highlight. I want this to be accurate, but I do NOT want it to make requests to GitHub every time a user visits. Instead, please implement a solution which will run once a day, fetch the number of stars from a list of repos, then write them to vercel's KV store. Finally, update the UI to make a request to the KV store when the user visits my page and render the accurate star counts."; const configurable: Omit< GraphConfig["configurable"], "thread_id" | "assistant_id" > = { target_repository: { - owner: "YOUR_USERNAME", - repo: "YOUR_REPO", - branch: "OPTIONAL BRANCH NAME", + owner: "bracesproul", + repo: "personal-site", }, }; - const stream = client.runs.stream(threadId, "open-codex", { + const stream = client.runs.stream(threadId, "open-swe", { input: { messages: [{ role: "user", content: userRequest }], }, @@ -38,6 +38,8 @@ async function runE2E() { streamMode: "updates", }); + console.log(`\nRun started with thread ID: "${threadId}"\n`); + for await (const chunk of stream) { console.dir(chunk.data, { depth: null }); } @@ -57,7 +59,7 @@ async function resumeGraph(threadId: string) { }, ]; - const stream = client.runs.stream(threadId, "open-codex", { + const stream = client.runs.stream(threadId, "open-swe", { command: { resume: resumeValue, }, diff --git a/scripts/run-from-plan.ts b/scripts/run-from-plan.ts index 7109d987..22468181 100644 --- a/scripts/run-from-plan.ts +++ b/scripts/run-from-plan.ts @@ -125,7 +125,7 @@ async function runFromPlan() { `Sandbox initialized successfully. ID: ${initResult.sandboxSessionId}\n\n`, ); - const stream = client.runs.stream(threadId, "open-codex", { + const stream = client.runs.stream(threadId, "open-swe", { command: { goto: "generate-action", update: { diff --git a/src/nodes/generate-message.ts b/src/nodes/generate-message.ts index 3ebea352..d08c1173 100644 --- a/src/nodes/generate-message.ts +++ b/src/nodes/generate-message.ts @@ -29,6 +29,7 @@ You MUST adhere to the following criteria when executing the task: - If completing the user's task requires writing or modifying files: - Your code and final answer should follow these *CODING GUIDELINES*: - Avoid writing to files which you have not already read. + - If writing to an empty file, or completely overwriting a file, you should not use \`apply_patch\` and instead use \`cat\` via the \`shell\` tool to write the file contents directly. - Fix the problem at the root cause rather than applying surface-level patches, when possible. - Avoid unneeded complexity in your solution. - Ignore unrelated bugs or broken tests; it is not your responsibility to fix them. diff --git a/src/nodes/initialize.ts b/src/nodes/initialize.ts index a6eaace7..a8d49554 100644 --- a/src/nodes/initialize.ts +++ b/src/nodes/initialize.ts @@ -12,6 +12,7 @@ import { getBranchName, getRepoAbsolutePath, } from "../utils/git/index.js"; +import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js"; const SANDBOX_TEMPLATE_ID = "eh0860emqx28qyxmbctu"; @@ -20,23 +21,29 @@ async function cloneRepo(sandbox: Sandbox, targetRepository: TargetRepository) { throw new Error("GITHUB_PAT environment variable not set."); } - const gitCloneCommand = ["git", "clone"]; + try { + const gitCloneCommand = ["git", "clone"]; - const repoUrlWithToken = `https://${process.env.GITHUB_PAT}@github.com/${targetRepository.owner}/${targetRepository.repo}.git`; + const repoUrlWithToken = `https://${process.env.GITHUB_PAT}@github.com/${targetRepository.owner}/${targetRepository.repo}.git`; - if (targetRepository.branch) { - gitCloneCommand.push("-b", targetRepository.branch, repoUrlWithToken); - } else { - gitCloneCommand.push(repoUrlWithToken); + if (targetRepository.branch) { + gitCloneCommand.push("-b", targetRepository.branch, repoUrlWithToken); + } else { + gitCloneCommand.push(repoUrlWithToken); + } + + console.log("Cloning repository...", { + command: gitCloneCommand.join(" "), + }); + return await sandbox.commands.run( + gitCloneCommand.join(" "), + TIMEOUT_EXTENSION_OPT, + ); + } catch (e) { + const errorFields = getSandboxErrorFields(e); + console.error("Failed to clone repository", errorFields ?? e); + throw e; } - - console.log("Cloning repository...", { - command: gitCloneCommand.join(" "), - }); - return await sandbox.commands.run( - gitCloneCommand.join(" "), - TIMEOUT_EXTENSION_OPT, - ); } /** diff --git a/src/tools/apply-patch.ts b/src/tools/apply-patch.ts index f7b13dd8..b0c8f19c 100644 --- a/src/tools/apply-patch.ts +++ b/src/tools/apply-patch.ts @@ -37,7 +37,14 @@ export const applyPatchTool = tool( console.log(`\nApplying patch to file ${file_path}\n`); console.log("\nreadFileOutput\n", readFileOutput); console.log("\ndiff\n", diff); - const patchedContent = applyPatch(readFileOutput, diff); + + let patchedContent: string | false; + try { + patchedContent = applyPatch(readFileOutput, diff); + } catch (e) { + console.error("Failed to apply patch", e); + 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)}...`; + } if (patchedContent === false) { 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)}...`;