mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 18:33:15 +00:00
feat: Add install dependencies tool (#291)
* feat: Add install dependencies tool * cr * hookup prompt * frontend * add prompt saying deps installed
This commit is contained in:
parent
5ab7b94be9
commit
08a0945a0e
13 changed files with 318 additions and 29 deletions
12
apps/open-swe/src/constants.ts
Normal file
12
apps/open-swe/src/constants.ts
Normal file
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
101
apps/open-swe/src/tools/install-dependencies.ts
Normal file
101
apps/open-swe/src/tools/install-dependencies.ts
Normal file
|
|
@ -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<GraphState, "sandboxSessionId" | "targetRepository">,
|
||||
) {
|
||||
const installDependenciesTool = tool(
|
||||
async (input): Promise<{ result: string; status: "success" | "error" }> => {
|
||||
let sandbox: Sandbox | undefined;
|
||||
try {
|
||||
const state = getCurrentTaskInput<GraphState>();
|
||||
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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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<typeof shellTool.schema>;
|
||||
const applyPatchTool = createApplyPatchToolFields(dummyRepo);
|
||||
type ApplyPatchToolArgs = z.infer<typeof applyPatchTool.schema>;
|
||||
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<InstallDependenciesToolArgs> & {
|
||||
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 <Loader2 className="text-muted-foreground mr-2 size-3.5" />;
|
||||
}
|
||||
|
||||
return props.actionType === "shell" ? (
|
||||
<Terminal className="text-muted-foreground mr-2 size-3.5" />
|
||||
) : (
|
||||
<FileCode className="text-muted-foreground mr-2 size-3.5" />
|
||||
);
|
||||
if (props.actionType === "install_dependencies") {
|
||||
return <CloudDownload className="text-muted-foreground mr-2 size-3.5" />;
|
||||
} else if (props.actionType === "apply-patch") {
|
||||
return <FileCode className="text-muted-foreground mr-2 size-3.5" />;
|
||||
} else if (props.actionType === "rg") {
|
||||
return <Search className="text-muted-foreground mr-2 size-3.5" />;
|
||||
} else {
|
||||
return <Terminal className="text-muted-foreground mr-2 size-3.5" />;
|
||||
}
|
||||
};
|
||||
|
||||
// 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 (
|
||||
|
|
|
|||
|
|
@ -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<typeof setTaskStatusTool.schema>;
|
||||
const rgTool = createRgToolFields(dummyRepo);
|
||||
type RgToolArgs = z.infer<typeof rgTool.schema>;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<boolean>(), {
|
||||
reducer: {
|
||||
schema: z.custom<boolean>(),
|
||||
fn: (_state, update) => update,
|
||||
},
|
||||
default: () => false,
|
||||
}),
|
||||
|
||||
// ---NOT USED---
|
||||
ui: z
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue