From 08a0945a0ea4df3ed645a2c8fdcb5f208a8b6d01 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Sun, 22 Jun 2025 15:47:29 -0700 Subject: [PATCH] feat: Add install dependencies tool (#291) * feat: Add install dependencies tool * cr * hookup prompt * frontend * add prompt saying deps installed --- apps/open-swe/src/constants.ts | 12 +++ .../nodes/generate-message/index.ts | 19 +++- .../nodes/generate-message/prompt.ts | 4 + .../src/graphs/programmer/nodes/open-pr.ts | 7 +- .../graphs/programmer/nodes/take-action.ts | 16 +++ .../src/graphs/shared/initialize-sandbox.ts | 10 +- apps/open-swe/src/tests/sandbox.int.test.ts | 58 ++++++++-- .../src/tools/install-dependencies.ts | 101 ++++++++++++++++++ apps/open-swe/src/utils/truncate-outputs.ts | 7 +- .../web/src/components/gen-ui/action-step.tsx | 48 +++++++-- .../web/src/components/thread/messages/ai.tsx | 30 +++++- packages/shared/src/open-swe/tools.ts | 25 +++++ packages/shared/src/open-swe/types.ts | 10 ++ 13 files changed, 318 insertions(+), 29 deletions(-) create mode 100644 apps/open-swe/src/constants.ts create mode 100644 apps/open-swe/src/tools/install-dependencies.ts diff --git a/apps/open-swe/src/constants.ts b/apps/open-swe/src/constants.ts new file mode 100644 index 00000000..2ab44ac2 --- /dev/null +++ b/apps/open-swe/src/constants.ts @@ -0,0 +1,12 @@ +import { SNAPSHOT_NAME } from "@open-swe/shared/constants"; +import { CreateSandboxParams } from "@daytonaio/sdk"; + +export const DEFAULT_SANDBOX_CREATE_PARAMS: CreateSandboxParams = { + resources: { + cpu: 2, + memory: 4, + disk: 5, + }, + user: "daytona", + image: SNAPSHOT_NAME, +}; diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts index 4edfb3a4..0845be72 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts @@ -16,11 +16,16 @@ import { createLogger, LogLevel } from "../../../../utils/logger.js"; import { getCurrentPlanItem } from "../../../../utils/current-task.js"; import { getMessageContentString } from "@open-swe/shared/messages"; import { getActivePlanItems } from "@open-swe/shared/open-swe/tasks"; -import { SYSTEM_PROMPT } from "./prompt.js"; +import { + DEPENDENCIES_INSTALLED_PROMPT, + INSTALL_DEPENDENCIES_TOOL_PROMPT, + SYSTEM_PROMPT, +} from "./prompt.js"; import { getRepoAbsolutePath } from "@open-swe/shared/git"; import { getMissingMessages } from "../../../../utils/github/issue-messages.js"; import { getTaskPlanFromIssue } from "../../../../utils/github/issue-task.js"; import { createRgTool } from "../../../../tools/rg.js"; +import { createInstallDependenciesTool } from "../../../../tools/install-dependencies.js"; const logger = createLogger(LogLevel.INFO, "GenerateMessageNode"); @@ -50,7 +55,13 @@ const formatPrompt = (state: GraphState): string => { state.codebaseTree || "No codebase tree generated yet.", ) .replaceAll("{CURRENT_WORKING_DIRECTORY}", repoDirectory) - .replaceAll("{CURRENT_TASK_NUMBER}", currentPlanItem.index.toString()); + .replaceAll("{CURRENT_TASK_NUMBER}", currentPlanItem.index.toString()) + .replaceAll( + "{INSTALL_DEPENDENCIES_TOOL_PROMPT}", + !state.dependenciesInstalled + ? INSTALL_DEPENDENCIES_TOOL_PROMPT + : DEPENDENCIES_INSTALLED_PROMPT, + ); }; export async function generateAction( @@ -64,6 +75,10 @@ export async function generateAction( createApplyPatchTool(state), createRequestHumanHelpToolFields(), createUpdatePlanToolFields(), + // Only provide the dependencies installed tool if they're not already installed. + ...(state.dependenciesInstalled + ? [] + : [createInstallDependenciesTool(state)]), ]; const modelWithTools = model.bindTools(tools, { tool_choice: "auto", diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts index 5e48b25e..b4f826ef 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/prompt.ts @@ -1,3 +1,6 @@ +export const INSTALL_DEPENDENCIES_TOOL_PROMPT = `* Use \`install_dependencies\` to install dependencies (skip if installation fails). IMPORTANT: You should only call this tool if you're executing a task which REQUIRES installing dependencies. Keep in mind that not all tasks will require installing dependencies.`; +export const DEPENDENCIES_INSTALLED_PROMPT = `* Dependencies have already been installed. *`; + export const SYSTEM_PROMPT = `# Identity You are a terminal-based agentic coding assistant built by LangChain. You wrap LLM models to enable natural language interaction with local codebases. You are precise, safe, and helpful. @@ -33,6 +36,7 @@ You are currently executing a specific task from a pre-generated plan. You have * Use \`apply_patch\` for file edits (accepts diffs and file paths) * Use \`shell\` with \`touch\` to create new files (not \`apply_patch\`) * Always use \`workdir\` parameter instead of \`cd\` when running commands via the \`shell\` tool +{INSTALL_DEPENDENCIES_TOOL_PROMPT} ### Tool Usage Best Practices diff --git a/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts b/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts index a6655850..98a2a000 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/open-pr.ts @@ -159,7 +159,10 @@ export async function openPullRequest( return { messages: newMessages, internalMessages: newMessages, - // If the sandbox was successfully deleted, we can remove it from the state. - ...(sandboxDeleted && { sandboxSessionId: undefined }), + // If the sandbox was successfully deleted, we can remove it from the state & reset the dependencies installed flag. + ...(sandboxDeleted && { + sandboxSessionId: undefined, + dependenciesInstalled: false, + }), }; } diff --git a/apps/open-swe/src/graphs/programmer/nodes/take-action.ts b/apps/open-swe/src/graphs/programmer/nodes/take-action.ts index 2a59c2b8..39e55f8d 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/take-action.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/take-action.ts @@ -20,6 +20,8 @@ import { daytonaClient } from "../../../utils/sandbox.js"; import { getCodebaseTree } from "../../../utils/tree.js"; import { getRepoAbsolutePath } from "@open-swe/shared/git"; import { shouldDiagnoseError } from "../utils/tool-message-error.js"; +import { createInstallDependenciesTool } from "../../../tools/install-dependencies.js"; +import { createRgTool } from "../../../tools/rg.js"; const logger = createLogger(LogLevel.INFO, "TakeAction"); @@ -41,9 +43,13 @@ export async function takeAction( const applyPatchTool = createApplyPatchTool(state); const shellTool = createShellTool(state); + const rgTool = createRgTool(state); + const installDependenciesTool = createInstallDependenciesTool(state); const toolsMap = { [applyPatchTool.name]: applyPatchTool, [shellTool.name]: shellTool, + [rgTool.name]: rgTool, + [installDependenciesTool.name]: installDependenciesTool, }; const toolCalls = lastMessage.tool_calls; @@ -107,6 +113,13 @@ export async function takeAction( const toolCallResults = await Promise.all(toolCallResultsPromise); + let wereDependenciesInstalled: boolean | null = null; + toolCallResults.forEach((toolCallResult) => { + if (toolCallResult.name === installDependenciesTool.name) { + wereDependenciesInstalled = toolCallResult.status === "success"; + } + }); + // Always check if there are changed files after running a tool. // If there are, commit them. const sandbox = await daytonaClient().get(state.sandboxSessionId); @@ -142,6 +155,9 @@ export async function takeAction( internalMessages: toolCallResults, ...(branchName && { branchName }), codebaseTree, + ...(wereDependenciesInstalled !== null && { + dependenciesInstalled: wereDependenciesInstalled, + }), }; return new Command({ goto: shouldRouteDiagnoseNode ? "diagnose-error" : "progress-plan-step", diff --git a/apps/open-swe/src/graphs/shared/initialize-sandbox.ts b/apps/open-swe/src/graphs/shared/initialize-sandbox.ts index eb0cbe87..58173fd8 100644 --- a/apps/open-swe/src/graphs/shared/initialize-sandbox.ts +++ b/apps/open-swe/src/graphs/shared/initialize-sandbox.ts @@ -11,16 +11,14 @@ import { pullLatestChanges, } from "../../utils/github/git.js"; import { getCodebaseTree } from "../../utils/tree.js"; -import { - DO_NOT_RENDER_ID_PREFIX, - SNAPSHOT_NAME, -} from "@open-swe/shared/constants"; +import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants"; import { CustomNodeEvent, INITIALIZE_NODE_ID, } from "@open-swe/shared/open-swe/custom-node-events"; import { Sandbox } from "@daytonaio/sdk"; import { AIMessage, BaseMessage } from "@langchain/core/messages"; +import { DEFAULT_SANDBOX_CREATE_PARAMS } from "../../constants.js"; const logger = createLogger(LogLevel.INFO, "InitializeSandbox"); @@ -30,6 +28,7 @@ type InitializeSandboxState = { sandboxSessionId?: string; codebaseTree?: string; messages?: BaseMessage[]; + dependenciesInstalled?: boolean; }; export async function initializeSandbox( @@ -207,7 +206,7 @@ export async function initializeSandbox( emitStepEvent(baseCreateSandboxAction, "pending"); let sandbox: Sandbox; try { - sandbox = await daytonaClient().create({ image: SNAPSHOT_NAME }); + sandbox = await daytonaClient().create(DEFAULT_SANDBOX_CREATE_PARAMS); emitStepEvent(baseCreateSandboxAction, "success"); } catch { emitStepEvent( @@ -332,5 +331,6 @@ export async function initializeSandbox( targetRepository, codebaseTree, messages: createEventsMessage(), + dependenciesInstalled: false, }; } diff --git a/apps/open-swe/src/tests/sandbox.int.test.ts b/apps/open-swe/src/tests/sandbox.int.test.ts index a42c2523..40ebd859 100644 --- a/apps/open-swe/src/tests/sandbox.int.test.ts +++ b/apps/open-swe/src/tests/sandbox.int.test.ts @@ -1,9 +1,10 @@ /* eslint-disable no-console */ import { test, expect } from "@jest/globals"; import { daytonaClient } from "../utils/sandbox.js"; -import { SNAPSHOT_NAME } from "@open-swe/shared/constants"; +import { SANDBOX_ROOT_DIR } from "@open-swe/shared/constants"; +import { DEFAULT_SANDBOX_CREATE_PARAMS } from "../constants.js"; -test("Can execute rg commands", async () => { +test.skip("Can execute rg commands", async () => { const githubToken = process.env.GITHUB_PAT; if (!githubToken) { throw new Error("GITHUB_PAT environment variable is not set"); @@ -12,10 +13,7 @@ test("Can execute rg commands", async () => { const client = daytonaClient(); console.log("Setting up sandbox..."); - const sandbox = await client.create({ - image: SNAPSHOT_NAME, - user: "daytona", - }); + const sandbox = await client.create(DEFAULT_SANDBOX_CREATE_PARAMS); console.log("Setup sandbox:", sandbox.id); const repoUrlWithToken = `https://x-access-token:${githubToken}@github.com/langchain-ai/open-swe.git`; @@ -41,3 +39,51 @@ OPEN_SWE_X expect(testRes.exitCode).toBe(0); }); + +const DEFAULT_ENV = { + // Prevents corepack from showing a y/n download prompt which causes the command to hang + COREPACK_ENABLE_DOWNLOAD_PROMPT: "0", +}; + +test("Installing dependencies", async () => { + const githubToken = process.env.GITHUB_PAT; + if (!githubToken) { + throw new Error("GITHUB_PAT environment variable is not set"); + } + + const client = daytonaClient(); + + console.log("Setting up sandbox..."); + const sandbox = await client.create(DEFAULT_SANDBOX_CREATE_PARAMS); + console.log("Setup sandbox:", sandbox.id); + + try { + const repoUrlWithToken = `https://x-access-token:${githubToken}@github.com/langchain-ai/open-swe.git`; + const cloneCommand = `git clone ${repoUrlWithToken}`; + + console.log("Cloning repo..."); + const cloneRes = await sandbox.process.executeCommand( + cloneCommand, + SANDBOX_ROOT_DIR, + ); + expect(cloneRes.exitCode).toBe(0); + + const installCommand = "yarn install"; + const installRes = await sandbox.process.executeCommand( + installCommand, + `${SANDBOX_ROOT_DIR}/open-swe`, + DEFAULT_ENV, + 120, // 120 seconds timeout + ); + console.log( + `install res status: ${installRes.exitCode}\ninstall res output: ${installRes.result}`, + ); + console.log( + `Install res exit code: ${installRes.exitCode}\nInstall res output: ${installRes.result}`, + ); + expect(installRes.exitCode).toBe(0); + } finally { + await sandbox.delete(); + console.log("Deleted sandbox:", sandbox.id); + } +}, 300_000); // 5 minutes timeout diff --git a/apps/open-swe/src/tools/install-dependencies.ts b/apps/open-swe/src/tools/install-dependencies.ts new file mode 100644 index 00000000..2ac3db0a --- /dev/null +++ b/apps/open-swe/src/tools/install-dependencies.ts @@ -0,0 +1,101 @@ +import { tool } from "@langchain/core/tools"; +import { Sandbox } from "@daytonaio/sdk"; +import { GraphState } from "@open-swe/shared/open-swe/types"; +import { getCurrentTaskInput } from "@langchain/langgraph"; +import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js"; +import { createLogger, LogLevel } from "../utils/logger.js"; +import { daytonaClient } from "../utils/sandbox.js"; +import { TIMEOUT_SEC } from "@open-swe/shared/constants"; +import { createInstallDependenciesToolFields } from "@open-swe/shared/open-swe/tools"; +import { getRepoAbsolutePath } from "@open-swe/shared/git"; + +const logger = createLogger(LogLevel.INFO, "InstallDependenciesTool"); + +const DEFAULT_ENV = { + // Prevents corepack from showing a y/n download prompt which causes the command to hang + COREPACK_ENABLE_DOWNLOAD_PROMPT: "0", +}; + +export function createInstallDependenciesTool( + state: Pick, +) { + const installDependenciesTool = tool( + async (input): Promise<{ result: string; status: "success" | "error" }> => { + let sandbox: Sandbox | undefined; + try { + const state = getCurrentTaskInput(); + const { sandboxSessionId } = state; + if (!sandboxSessionId) { + logger.error( + "FAILED TO INSTALL DEPENDENCIES: No sandbox session ID provided", + { + input, + }, + ); + throw new Error( + "FAILED TO INSTALL DEPENDENCIES: No sandbox session ID provided", + ); + } + + const repoRoot = getRepoAbsolutePath(state.targetRepository); + + sandbox = await daytonaClient().get(sandboxSessionId); + const command = input.command.join(" "); + const workdir = input.workdir || repoRoot; + logger.info("Running install dependencies command", { + command, + workdir, + }); + const response = await sandbox.process.executeCommand( + command, + workdir, + DEFAULT_ENV, + TIMEOUT_SEC * 2.5, // add a 2.5 min timeout + ); + + if (response.exitCode !== 0) { + logger.error("Failed to install dependencies", { + error: response.result, + error_result: response, + input, + }); + throw new Error( + `Command failed. Exit code: ${response.exitCode}\nResult: ${response.result}\nStdout:\n${response.artifacts?.stdout}`, + ); + } + + return { + result: response.result, + status: "success", + }; + } catch (e) { + const errorFields = getSandboxErrorFields(e); + if (errorFields) { + logger.error("Failed to install dependencies", { + input, + error: errorFields, + }); + throw new Error( + `Command failed. Exit code: ${errorFields.exitCode}\nError: ${errorFields.result ?? errorFields.artifacts?.stdout}`, + ); + } + + logger.error( + "Failed to install dependencies: " + + (e instanceof Error ? e.message : "Unknown error"), + { + error: e, + input, + }, + ); + throw new Error( + "FAILED TO INSTALL DEPENDENCIES: " + + (e instanceof Error ? e.message : "Unknown error"), + ); + } + }, + createInstallDependenciesToolFields(state.targetRepository), + ); + + return installDependenciesTool; +} diff --git a/apps/open-swe/src/utils/truncate-outputs.ts b/apps/open-swe/src/utils/truncate-outputs.ts index b17d96ff..30a744c3 100644 --- a/apps/open-swe/src/utils/truncate-outputs.ts +++ b/apps/open-swe/src/utils/truncate-outputs.ts @@ -6,18 +6,17 @@ export function truncateOutput( output: string, options?: { /** - * @default 10000 + * @default 2500 */ numStartCharacters?: number; /** - * @default 10000 + * @default 2500 */ numEndCharacters?: number; }, ) { - const { numStartCharacters = 10000, numEndCharacters = 10000 } = - options ?? {}; + const { numStartCharacters = 2500, numEndCharacters = 2500 } = options ?? {}; if (numStartCharacters < 0 || numEndCharacters < 0) { throw new Error("numStartCharacters and numEndCharacters must be >= 0"); diff --git a/apps/web/src/components/gen-ui/action-step.tsx b/apps/web/src/components/gen-ui/action-step.tsx index be1c0dd4..e1357953 100644 --- a/apps/web/src/components/gen-ui/action-step.tsx +++ b/apps/web/src/components/gen-ui/action-step.tsx @@ -11,10 +11,13 @@ import { ChevronUp, MessageSquare, FileText, + CloudDownload, + Search, } from "lucide-react"; import { createApplyPatchToolFields, createShellToolFields, + createInstallDependenciesToolFields, formatRgCommand, RipgrepCommand, } from "@open-swe/shared/open-swe/tools"; @@ -26,6 +29,10 @@ const shellTool = createShellToolFields(dummyRepo); type ShellToolArgs = z.infer; const applyPatchTool = createApplyPatchToolFields(dummyRepo); type ApplyPatchToolArgs = z.infer; +const installDependenciesTool = createInstallDependenciesToolFields(dummyRepo); +type InstallDependenciesToolArgs = z.infer< + typeof installDependenciesTool.schema +>; // Common props for all action types type BaseActionProps = { @@ -59,11 +66,19 @@ type RgActionProps = BaseActionProps & errorCode?: number; }; +type InstallDependenciesActionProps = BaseActionProps & + Partial & { + actionType: "install_dependencies"; + output?: string; + errorCode?: number; + }; + export type ActionItemProps = | (BaseActionProps & { status: "loading" }) | ShellActionProps | PatchActionProps - | RgActionProps; + | RgActionProps + | InstallDependenciesActionProps; export type ActionStepProps = { actions: ActionItemProps[]; @@ -109,6 +124,8 @@ function ActionItem(props: ActionItemProps) { return props.success ? "Patch applied" : "Patch failed"; } else if (props.actionType === "rg") { return props.success ? "Search completed" : "Search failed"; + } else if (props.actionType === "install_dependencies") { + return props.success ? "Dependencies installed" : "Installation failed"; } } @@ -119,7 +136,11 @@ function ActionItem(props: ActionItemProps) { const shouldShowToggle = () => { if (props.status !== "done") return false; - if (props.actionType === "shell" || props.actionType === "rg") { + if ( + props.actionType === "shell" || + props.actionType === "rg" || + props.actionType === "install_dependencies" + ) { return !!props.output; } else if (props.actionType === "apply-patch") { return !!props.diff; @@ -135,11 +156,15 @@ function ActionItem(props: ActionItemProps) { return ; } - return props.actionType === "shell" ? ( - - ) : ( - - ); + if (props.actionType === "install_dependencies") { + return ; + } else if (props.actionType === "apply-patch") { + return ; + } else if (props.actionType === "rg") { + return ; + } else { + return ; + } }; // Render the header content based on action type @@ -152,7 +177,10 @@ function ActionItem(props: ActionItemProps) { ); } - if (props.actionType === "shell") { + if ( + props.actionType === "shell" || + props.actionType === "install_dependencies" + ) { let commandStr = ""; if (props.command) { if (Array.isArray(props.command)) { @@ -215,7 +243,9 @@ function ActionItem(props: ActionItemProps) { if (!expanded) return null; if ( - (props.actionType === "shell" || props.actionType === "rg") && + (props.actionType === "shell" || + props.actionType === "rg" || + props.actionType === "install_dependencies") && props.output ) { return ( diff --git a/apps/web/src/components/thread/messages/ai.tsx b/apps/web/src/components/thread/messages/ai.tsx index 1e398e2a..5465e27c 100644 --- a/apps/web/src/components/thread/messages/ai.tsx +++ b/apps/web/src/components/thread/messages/ai.tsx @@ -26,6 +26,7 @@ import { createShellToolFields, createSetTaskStatusToolFields, createRgToolFields, + createInstallDependenciesToolFields, } from "@open-swe/shared/open-swe/tools"; import { z } from "zod"; import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages"; @@ -41,6 +42,10 @@ const setTaskStatusTool = createSetTaskStatusToolFields(); type SetTaskStatusToolArgs = z.infer; const rgTool = createRgToolFields(dummyRepo); type RgToolArgs = z.infer; +const installDependenciesTool = createInstallDependenciesToolFields(dummyRepo); +type InstallDependenciesToolArgs = z.infer< + typeof installDependenciesTool.schema +>; function CustomComponent({ message, @@ -148,6 +153,17 @@ export function mapToolMessageToActionStepProps( output: getContentString(message.content), reasoningText, }; + } else if (toolCall?.name === installDependenciesTool.name) { + const args = toolCall.args as InstallDependenciesToolArgs; + return { + actionType: "install_dependencies", + status, + success, + command: args.command || "", + workdir: args.workdir || "", + output: getContentString(message.content), + reasoningText, + }; } return { status: "loading", @@ -210,7 +226,8 @@ export function AssistantMessage({ (tc) => tc.name === shellTool.name || tc.name === applyPatchTool.name || - tc.name === rgTool.name, + tc.name === rgTool.name || + tc.name === installDependenciesTool.name, ) : []; @@ -248,6 +265,8 @@ export function AssistantMessage({ const isShellTool = toolCall.name === shellTool.name; const isRgTool = toolCall.name === rgTool.name; + const isInstallDependenciesTool = + toolCall.name === installDependenciesTool.name; if (correspondingToolResult) { // If we have a tool result, map it to action props @@ -261,6 +280,15 @@ export function AssistantMessage({ paths: args?.paths || [], output: "", } as ActionItemProps; + } else if (isInstallDependenciesTool) { + const args = toolCall.args as InstallDependenciesToolArgs; + return { + actionType: "install_dependencies", + status: "generating", + command: args?.command || "", + workdir: args?.workdir || "", + output: "", + } as ActionItemProps; } else { if (isShellTool) { const args = toolCall.args as ShellToolArgs; diff --git a/packages/shared/src/open-swe/tools.ts b/packages/shared/src/open-swe/tools.ts index 17bc96b9..7d5f8a46 100644 --- a/packages/shared/src/open-swe/tools.ts +++ b/packages/shared/src/open-swe/tools.ts @@ -179,3 +179,28 @@ export function createSetTaskStatusToolFields() { return setTaskStatusTool; } + +export function createInstallDependenciesToolFields( + targetRepository: TargetRepository, +) { + const repoRoot = getRepoAbsolutePath(targetRepository); + + const installDependenciesToolSchema = z.object({ + command: z + .array(z.string()) + .describe("The command to run to install dependencies."), + workdir: z + .string() + .default(repoRoot) + .describe( + `The working directory to run the command in. The default working directory this command will be executed in is the root of the repository: \`${repoRoot}\`. If you want to execute this install command inside a different location, pass a path to this field.`, + ), + }); + + return { + name: "install_dependencies", + description: + "Installs dependencies for the repository. You should only call this tool if you need to install dependencies for a specific task. Ensure you only call this tool after gathering context on how to install dependencies, such as the package manager, proper install command, etc.", + schema: installDependenciesToolSchema, + }; +} diff --git a/packages/shared/src/open-swe/types.ts b/packages/shared/src/open-swe/types.ts index 1d562ed1..259098c3 100644 --- a/packages/shared/src/open-swe/types.ts +++ b/packages/shared/src/open-swe/types.ts @@ -205,6 +205,16 @@ export const GraphAnnotation = MessagesZodState.extend({ fn: (_state, update) => update, }, }), + /** + * Whether or not the dependencies have been installed already in the sandbox. + */ + dependenciesInstalled: withLangGraph(z.custom(), { + reducer: { + schema: z.custom(), + fn: (_state, update) => update, + }, + default: () => false, + }), // ---NOT USED--- ui: z