mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 09:13:14 +00:00
feat: Open pull request in last step (#32)
* feat: Open pull request in last step * cr * cr
This commit is contained in:
parent
76dadeeb72
commit
b9d15e9f27
7 changed files with 357 additions and 2 deletions
|
|
@ -29,6 +29,7 @@
|
|||
"@langchain/langgraph": "^0.2.73",
|
||||
"@langchain/langgraph-sdk": "^0.0.78",
|
||||
"@langchain/openai": "^0.5.10",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"diff": "^8.0.1",
|
||||
"langchain": "^0.3.26",
|
||||
"langsmith": "^0.3.29",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
progressPlanStep,
|
||||
summarizeTaskSteps,
|
||||
generateConclusion,
|
||||
openPullRequest,
|
||||
diagnoseError,
|
||||
} from "./nodes/index.js";
|
||||
import { isAIMessage } from "@langchain/core/messages";
|
||||
|
|
@ -54,6 +55,7 @@ const workflow = new StateGraph(GraphAnnotation, GraphConfiguration)
|
|||
ends: ["generate-action", "generate-conclusion"],
|
||||
})
|
||||
.addNode("generate-conclusion", generateConclusion)
|
||||
.addNode("open-pr", openPullRequest)
|
||||
.addNode("diagnose-error", diagnoseError)
|
||||
.addEdge(START, "initialize")
|
||||
.addEdge("initialize", "generate-plan-subgraph")
|
||||
|
|
@ -61,8 +63,9 @@ const workflow = new StateGraph(GraphAnnotation, GraphConfiguration)
|
|||
// Always interrupt after rewriting the plan.
|
||||
.addEdge("rewrite-plan", "interrupt-plan")
|
||||
.addConditionalEdges("generate-action", takeActionOrEnd, ["take-action", END])
|
||||
.addEdge("generate-conclusion", "open-pr")
|
||||
.addEdge("diagnose-error", "generate-action")
|
||||
.addEdge("generate-conclusion", END);
|
||||
.addEdge("open-pr", END);
|
||||
|
||||
// Zod types are messed up
|
||||
export const graph = workflow.compile() as any;
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ export * from "./interrupt-plan.js";
|
|||
export * from "./progress-plan-step.js";
|
||||
export * from "./summarize-task-steps.js";
|
||||
export * from "./generate-conclusion.js";
|
||||
export * from "./open-pr.js";
|
||||
export * from "./diagnose-error.js";
|
||||
|
|
|
|||
148
apps/open-swe/src/nodes/open-pr.ts
Normal file
148
apps/open-swe/src/nodes/open-pr.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
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,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { Octokit } from "@octokit/rest";
|
||||
import { CommandResult, Sandbox } from "@e2b/code-interpreter";
|
||||
import { createLogger, LogLevel } from "../logger.js";
|
||||
import { GraphConfig } from "../../types.js";
|
||||
|
|
@ -390,3 +391,58 @@ export async function checkoutBranchAndCommit(
|
|||
|
||||
return branchName;
|
||||
}
|
||||
|
||||
export async function createPullRequest({
|
||||
owner,
|
||||
repo,
|
||||
headBranch,
|
||||
title,
|
||||
body = "",
|
||||
}: {
|
||||
owner: string;
|
||||
repo: string;
|
||||
headBranch: string;
|
||||
title: string;
|
||||
body?: string;
|
||||
}) {
|
||||
// Initialize Octokit with the personal access token
|
||||
const token = process.env.GITHUB_PAT;
|
||||
if (!token) {
|
||||
throw new Error("GITHUB_PAT environment variable is not set");
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: token,
|
||||
});
|
||||
|
||||
try {
|
||||
// Step 1: Get repository information to find the default branch
|
||||
const { data: repository } = await octokit.repos.get({
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
|
||||
const defaultBranch = repository.default_branch;
|
||||
logger.info(
|
||||
`Creating pull request against default branch: ${defaultBranch}`,
|
||||
);
|
||||
|
||||
// Step 2: Create the pull request
|
||||
const { data: pullRequest } = await octokit.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
title,
|
||||
body,
|
||||
head: headBranch,
|
||||
base: defaultBranch,
|
||||
});
|
||||
|
||||
logger.info(`🐙 Pull request created: ${pullRequest.html_url}`);
|
||||
return pullRequest;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to create pull request`, {
|
||||
error,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"outputs": [".dist/**"]
|
||||
"outputs": ["dist/**"]
|
||||
},
|
||||
"dev": {
|
||||
"dependsOn": ["^dev"]
|
||||
|
|
|
|||
146
yarn.lock
146
yarn.lock
|
|
@ -2472,6 +2472,130 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/auth-token@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@octokit/auth-token@npm:6.0.0"
|
||||
checksum: 9c23be526c7f8e282aa7ccec6f3a72a1beec44eae736327e9ba78419fa28ba75e2c686e9eac75f35ce99bdb55eff9605f7ef7588a9d4f4e18ad5ed16a5d887ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/core@npm:^7.0.2":
|
||||
version: 7.0.2
|
||||
resolution: "@octokit/core@npm:7.0.2"
|
||||
dependencies:
|
||||
"@octokit/auth-token": ^6.0.0
|
||||
"@octokit/graphql": ^9.0.1
|
||||
"@octokit/request": ^10.0.2
|
||||
"@octokit/request-error": ^7.0.0
|
||||
"@octokit/types": ^14.0.0
|
||||
before-after-hook: ^4.0.0
|
||||
universal-user-agent: ^7.0.0
|
||||
checksum: 569e65c7fc23518c5e058aad280b669f33a82fa9f4c26afa4481ed0488147ec779907b3cd0a333eda850b0127ab46dc0caeeb27fb72eca0ed60db23c30d75bf0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/endpoint@npm:^11.0.0":
|
||||
version: 11.0.0
|
||||
resolution: "@octokit/endpoint@npm:11.0.0"
|
||||
dependencies:
|
||||
"@octokit/types": ^14.0.0
|
||||
universal-user-agent: ^7.0.2
|
||||
checksum: 1c4bd71b3041bf935535c13e9636cb9846469655050583e0ad2595f2f1f840eba2a3f5f43a0dbc82fd695ad0124ab4fc389a2ef3d0770d642fed717e31e4300f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/graphql@npm:^9.0.1":
|
||||
version: 9.0.1
|
||||
resolution: "@octokit/graphql@npm:9.0.1"
|
||||
dependencies:
|
||||
"@octokit/request": ^10.0.2
|
||||
"@octokit/types": ^14.0.0
|
||||
universal-user-agent: ^7.0.0
|
||||
checksum: 3d59773cf56333be8668f7708c473f5746ad49552c0e542ae32c0442e0f16c4b6389408c8868f211b45d0292f1ebfdc92160ee6c2cbf12c5fa0d9a938713bcf9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/openapi-types@npm:^25.1.0":
|
||||
version: 25.1.0
|
||||
resolution: "@octokit/openapi-types@npm:25.1.0"
|
||||
checksum: 441b17f801254629b3ddb4b878c589fee1fd23015253c8b72a3acb3eeedbe981691bb311649ab5f955005c5d7adb940f19e18eaf0c875752fe0cc12b3dc1d24b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-paginate-rest@npm:^13.0.1":
|
||||
version: 13.0.1
|
||||
resolution: "@octokit/plugin-paginate-rest@npm:13.0.1"
|
||||
dependencies:
|
||||
"@octokit/types": ^14.1.0
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 4b74c601346a055d1ebf007137bc9e47f0a4276b0767fb7d833e81b3ffda5901b4f0e1bac0d059e733b2caddaeb7df0cae36b98d9085d1690aa81dcd5bb9994a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-request-log@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@octokit/plugin-request-log@npm:6.0.0"
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 8a79973b1429bfead9113c4117f418aaef5ff368795daded3415ba14623d97d5fc08d1e822dbd566ecc9f041119e1a48a11853a9c48d9eb1caa62baa79c17f83
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods@npm:^16.0.0":
|
||||
version: 16.0.0
|
||||
resolution: "@octokit/plugin-rest-endpoint-methods@npm:16.0.0"
|
||||
dependencies:
|
||||
"@octokit/types": ^14.1.0
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 8ebb30b41628b839cca0b1051459f92001db37f4c3b8a87551915cdf5707d9e87f300565795ca2c12b5767b0d294b15bb0cd1ab3da99620c82690b37eddbd635
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/request-error@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "@octokit/request-error@npm:7.0.0"
|
||||
dependencies:
|
||||
"@octokit/types": ^14.0.0
|
||||
checksum: c4370d2c31f599c1f366c480d5a02bc93442e5a0e151ec5caf0d5a5b0f0f91b50ecedc945aa6ea61b4c9ed1e89153dc7727daf4317680d33e916f829da7d141b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/request@npm:^10.0.2":
|
||||
version: 10.0.2
|
||||
resolution: "@octokit/request@npm:10.0.2"
|
||||
dependencies:
|
||||
"@octokit/endpoint": ^11.0.0
|
||||
"@octokit/request-error": ^7.0.0
|
||||
"@octokit/types": ^14.0.0
|
||||
fast-content-type-parse: ^3.0.0
|
||||
universal-user-agent: ^7.0.2
|
||||
checksum: 66e56fbf8d3df538c68881eab393e4ebca418204c6f452d39f95d050ec6151169efd1dac3cea9def3f1ebfc8ddc7fad7721f39d2f793f60d52d63cd386b11a90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/rest@npm:^22.0.0":
|
||||
version: 22.0.0
|
||||
resolution: "@octokit/rest@npm:22.0.0"
|
||||
dependencies:
|
||||
"@octokit/core": ^7.0.2
|
||||
"@octokit/plugin-paginate-rest": ^13.0.1
|
||||
"@octokit/plugin-request-log": ^6.0.0
|
||||
"@octokit/plugin-rest-endpoint-methods": ^16.0.0
|
||||
checksum: 6a7eff019c0889b23c0820831936e5dc8fa7643bdf0e98ba073b36a10f5602b9f283ca2c74ec8172b8529d0647dfa4a7857dcd81ca028b303937f26750a6c7f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/types@npm:^14.0.0, @octokit/types@npm:^14.1.0":
|
||||
version: 14.1.0
|
||||
resolution: "@octokit/types@npm:14.1.0"
|
||||
dependencies:
|
||||
"@octokit/openapi-types": ^25.1.0
|
||||
checksum: 0513520e26dc5395c3b3b407568151d32be1f51bedb151f5b294cadc72dc3fe2d0dbbccad96f01dc80d26247b4aed3358de0ce31ad3c013eb22b96e6234feeb5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@open-agent-platform/docs@workspace:apps/docs":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@open-agent-platform/docs@workspace:apps/docs"
|
||||
|
|
@ -2495,6 +2619,7 @@ __metadata:
|
|||
"@langchain/langgraph-cli": latest
|
||||
"@langchain/langgraph-sdk": ^0.0.78
|
||||
"@langchain/openai": ^0.5.10
|
||||
"@octokit/rest": ^22.0.0
|
||||
"@tsconfig/recommended": ^1.0.8
|
||||
"@types/jest": ^29.5.0
|
||||
"@types/node": ^22.13.5
|
||||
|
|
@ -5328,6 +5453,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"before-after-hook@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "before-after-hook@npm:4.0.0"
|
||||
checksum: a8cbd4d3c48f42f44307ef5966be152b836d2e5908834f2f885ddf104c2e2ba66dbb5e6ef89a37e77371b1d22d5c75b74df1472286c684a037c1a6db43f5617b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"better-opn@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "better-opn@npm:3.0.2"
|
||||
|
|
@ -7769,6 +7901,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-content-type-parse@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "fast-content-type-parse@npm:3.0.0"
|
||||
checksum: 490199423215b8a9c6e24a5a01a0d072af8ebfe24c13deac0a393dcac36b732295dd8cec5a2c4241249ed0fffc6983ba138f3001b13286afefb66360b6715a46
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "fast-deep-equal@npm:3.1.3"
|
||||
|
|
@ -15715,6 +15854,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2":
|
||||
version: 7.0.3
|
||||
resolution: "universal-user-agent@npm:7.0.3"
|
||||
checksum: c497e85f8b11eb8fa4dce584d7a39cc98710164959f494cafc3c269b51abb20fff269951838efd7424d15f6b3d001507f3cb8b52bb5676fdb642019dfd17e63e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universalify@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "universalify@npm:2.0.1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue