open-swe/apps/open-swe/src/nodes/open-pr.ts
Brace Sproul b9d15e9f27
feat: Open pull request in last step (#32)
* feat: Open pull request in last step

* cr

* cr
2025-05-28 19:29:57 +00:00

148 lines
4.3 KiB
TypeScript

import { GraphConfig, GraphState, GraphUpdate, PlanItem } from "../types.js";
import { Sandbox } from "@e2b/code-interpreter";
import {
checkoutBranchAndCommit,
createPullRequest,
getBranchName,
getChangedFilesStatus,
getRepoAbsolutePath,
} from "../utils/git/index.js";
import { createLogger, LogLevel } from "../utils/logger.js";
import { z } from "zod";
import { loadModel, Task } from "../utils/load-model.js";
import { formatPlanPromptWithSummaries } from "../utils/plan-prompt.js";
import { isHumanMessage, ToolMessage } from "@langchain/core/messages";
import { getMessageContentString } from "../utils/message/content.js";
const logger = createLogger(LogLevel.INFO, "Open PR");
const openPrSysPrompt = `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.
You have just completed all of your tasks, and are now ready to open a pull request.
Here are all of the tasks you completed:
{COMPLETED_TASKS}
And here is the user's original request:
{USER_REQUEST}
With all of this in mind, please use the \`open_pr\` tool to open a pull request.`;
const openPrToolSchema = z.object({
title: z
.string()
.describe(
"The title of the pull request. Ensure this is a concise and thoughtful title. You should follow conventional commit title format (e.g. prefixing with 'fix:', 'feat:', 'chore:', etc.).",
),
body: z
.string()
.optional()
.describe(
"The body of the pull request. This should provide a detailed description of the changes you've made, and why you've made them. Ensure you do not over-explain the changes, as we do not want to waste the user's time.",
),
});
const openPrTool = {
name: "open_pr",
schema: openPrToolSchema,
description: "Use this tool to open a pull request.",
};
const formatPrompt = (plan: PlanItem[], userRequest: string): string => {
const completedTasks = plan.filter((task) => task.completed);
return openPrSysPrompt
.replace("{COMPLETED_TASKS}", formatPlanPromptWithSummaries(completedTasks))
.replace("{USER_REQUEST}", userRequest);
};
export async function openPullRequest(
state: GraphState,
config: GraphConfig,
): Promise<GraphUpdate> {
const sandboxSessionId = state.sandboxSessionId;
if (!sandboxSessionId) {
throw new Error(
"Failed to open pull request: No sandbox session ID found in state.",
);
}
const sandbox = await Sandbox.connect(sandboxSessionId);
const { owner, repo } = config.configurable?.target_repository ?? {};
if (!owner || !repo) {
throw new Error(
"Failed to open pull request: No target repository found in config.",
);
}
const changedFiles = await getChangedFilesStatus(
getRepoAbsolutePath(config),
sandbox,
);
let branchName = state.branchName;
if (changedFiles.length > 0) {
logger.info(`Has ${changedFiles.length} changed files. Committing.`, {
changedFiles,
});
branchName = await checkoutBranchAndCommit(config, sandbox, {
branchName,
});
}
const model = await loadModel(config, Task.SUMMARIZER);
const modelWithTool = model.bindTools([openPrTool], {
tool_choice: openPrTool.name,
});
const firstUserMessage = state.messages.find(isHumanMessage);
if (!firstUserMessage) {
throw new Error(
"Failed to open pull request: No user message found in state.",
);
}
const response = await modelWithTool.invoke([
{
role: "user",
content: formatPrompt(
state.plan,
getMessageContentString(firstUserMessage.content),
),
},
]);
const toolCall = response.tool_calls?.[0];
if (!toolCall) {
throw new Error(
"Failed to generate a tool call when opening a pull request.",
);
}
const { title, body } = toolCall.args as z.infer<typeof openPrToolSchema>;
const pr = await createPullRequest({
owner,
repo,
headBranch: branchName ?? getBranchName(config),
title,
body,
});
return {
messages: [
response,
new ToolMessage({
tool_call_id: toolCall.id ?? "",
content: pr
? `Created pull request: ${pr.html_url}`
: "Failed to create pull request.",
name: toolCall.name,
additional_kwargs: {
pull_request: pr,
},
}),
],
};
}