mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 11:33:14 +00:00
refactor: move target_repository from configuration to state (#44)
* Apply patch * Apply patch * Apply patch * update frontend to reflect state change * update scripts * cr --------- Co-authored-by: Harrison Chase <11986836+hwchase17@users.noreply.github.com> Co-authored-by: bracesproul <braceasproul@gmail.com>
This commit is contained in:
parent
efba99a4f4
commit
ff23ea49a8
12 changed files with 68 additions and 106 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import "dotenv/config";
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { GraphConfig } from "../src/types.js";
|
||||
import { HumanResponse } from "@langchain/langgraph/prebuilt";
|
||||
import { createLogger, LogLevel } from "../src/utils/logger.js";
|
||||
|
||||
|
|
@ -25,22 +24,17 @@ I want the server to be able to authenticate users with GitHub, such that we wil
|
|||
3. make pull requests and push changes to the repositories they give us access to
|
||||
Once you're done, ensure you've documented the development process in the readme of this new app.`;
|
||||
|
||||
const configurable: Omit<
|
||||
GraphConfig["configurable"],
|
||||
"thread_id" | "assistant_id"
|
||||
> = {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
const targetRepository = {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
};
|
||||
|
||||
const stream = client.runs.stream(threadId, "open-swe", {
|
||||
input: {
|
||||
messages: [{ role: "user", content: userRequest }],
|
||||
targetRepository,
|
||||
},
|
||||
config: {
|
||||
configurable,
|
||||
recursion_limit: 400,
|
||||
},
|
||||
ifNotExists: "create",
|
||||
|
|
@ -69,22 +63,12 @@ async function resumeGraph(threadId: string) {
|
|||
args: null,
|
||||
},
|
||||
];
|
||||
const configurable: Omit<
|
||||
GraphConfig["configurable"],
|
||||
"thread_id" | "assistant_id"
|
||||
> = {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
};
|
||||
|
||||
const stream = client.runs.stream(threadId, "open-swe", {
|
||||
command: {
|
||||
resume: resumeValue,
|
||||
},
|
||||
config: {
|
||||
configurable,
|
||||
recursion_limit: 400,
|
||||
},
|
||||
streamSubgraphs: true,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import "dotenv/config";
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { GraphConfig } from "../src/types.js";
|
||||
import { graph } from "../src/index.js";
|
||||
import { createLogger, LogLevel } from "../src/utils/logger.js";
|
||||
|
||||
|
|
@ -15,7 +14,13 @@ async function runFromPlan() {
|
|||
|
||||
const threadId = uuidv4();
|
||||
|
||||
const targetRepository = {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
};
|
||||
|
||||
const inputs = {
|
||||
targetRepository,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
|
|
@ -111,21 +116,9 @@ The user wants to implement a GitHub OAuth authentication server in the \`/apps/
|
|||
branchName: `open-swe/${threadId}`,
|
||||
};
|
||||
|
||||
const configurable: Omit<
|
||||
GraphConfig["configurable"],
|
||||
"thread_id" | "assistant_id"
|
||||
> = {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
};
|
||||
|
||||
logger.info("Initializing sandbox...");
|
||||
|
||||
const initResult = await graph.nodes.initialize.invoke(inputs as any, {
|
||||
configurable,
|
||||
});
|
||||
const initResult = await graph.nodes.initialize.invoke(inputs as any);
|
||||
if (!initResult.sandboxSessionId) {
|
||||
throw new Error("Failed to initialize sandbox.");
|
||||
}
|
||||
|
|
@ -143,7 +136,6 @@ The user wants to implement a GitHub OAuth authentication server in the \`/apps/
|
|||
},
|
||||
},
|
||||
config: {
|
||||
configurable,
|
||||
recursion_limit: 400,
|
||||
},
|
||||
ifNotExists: "create",
|
||||
|
|
|
|||
|
|
@ -98,8 +98,8 @@ Once again, here are the completed tasks, remaining tasks, and the current task
|
|||
{PLAN_PROMPT}
|
||||
`;
|
||||
|
||||
const formatPrompt = (state: GraphState, config: GraphConfig): string => {
|
||||
const repoDirectory = getRepoAbsolutePath(config);
|
||||
const formatPrompt = (state: GraphState): string => {
|
||||
const repoDirectory = getRepoAbsolutePath(state.targetRepository);
|
||||
return systemPrompt
|
||||
.replaceAll(
|
||||
"{PLAN_PROMPT_WITH_SUMMARIES}",
|
||||
|
|
@ -128,7 +128,7 @@ export async function generateAction(
|
|||
const response = await modelWithTools.invoke([
|
||||
{
|
||||
role: "system",
|
||||
content: formatPrompt(state, config),
|
||||
content: formatPrompt(state),
|
||||
},
|
||||
...state.messages,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -83,11 +83,10 @@ export async function initialize(
|
|||
}
|
||||
}
|
||||
|
||||
const { target_repository } = config.configurable;
|
||||
|
||||
if (!target_repository) {
|
||||
const { targetRepository } = state;
|
||||
if (!targetRepository) {
|
||||
throw new Error(
|
||||
"Missing required configuration. Please provide a git repository URL.",
|
||||
"Missing required target repository. Please provide a git repository in state or configuration.",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +96,7 @@ export async function initialize(
|
|||
TIMEOUT_EXTENSION_OPT,
|
||||
);
|
||||
|
||||
const res = await cloneRepo(sandbox, target_repository);
|
||||
const res = await cloneRepo(sandbox, targetRepository);
|
||||
if (res.error) {
|
||||
// TODO: This should probably be an interrupt.
|
||||
logger.error("Failed to clone repository", res.error);
|
||||
|
|
@ -105,7 +104,7 @@ export async function initialize(
|
|||
}
|
||||
logger.info("Repository cloned successfully.");
|
||||
|
||||
const absoluteRepoDir = getRepoAbsolutePath(config);
|
||||
const absoluteRepoDir = getRepoAbsolutePath(targetRepository);
|
||||
|
||||
logger.info(`Configuring git user for repository at "${absoluteRepoDir}"...`);
|
||||
await configureGitUserInRepo(absoluteRepoDir, sandbox);
|
||||
|
|
@ -125,5 +124,6 @@ export async function initialize(
|
|||
|
||||
return {
|
||||
sandboxSessionId: sandbox.sandboxId,
|
||||
targetRepository,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export async function openPullRequest(
|
|||
|
||||
const sandbox = await Sandbox.connect(sandboxSessionId);
|
||||
|
||||
const { owner, repo } = config.configurable?.target_repository ?? {};
|
||||
const { owner, repo } = state.targetRepository;
|
||||
|
||||
if (!owner || !repo) {
|
||||
throw new Error(
|
||||
|
|
@ -77,7 +77,7 @@ export async function openPullRequest(
|
|||
}
|
||||
|
||||
const changedFiles = await getChangedFilesStatus(
|
||||
getRepoAbsolutePath(config),
|
||||
getRepoAbsolutePath(state.targetRepository),
|
||||
sandbox,
|
||||
);
|
||||
let branchName = state.branchName;
|
||||
|
|
@ -85,9 +85,14 @@ export async function openPullRequest(
|
|||
logger.info(`Has ${changedFiles.length} changed files. Committing.`, {
|
||||
changedFiles,
|
||||
});
|
||||
branchName = await checkoutBranchAndCommit(config, sandbox, {
|
||||
branchName,
|
||||
});
|
||||
branchName = await checkoutBranchAndCommit(
|
||||
config,
|
||||
state.targetRepository,
|
||||
sandbox,
|
||||
{
|
||||
branchName,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const model = await loadModel(config, Task.SUMMARIZER);
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export async function takeAction(
|
|||
// If there are, commit them.
|
||||
const sandbox = await Sandbox.connect(state.sandboxSessionId);
|
||||
const changedFiles = await getChangedFilesStatus(
|
||||
getRepoAbsolutePath(config),
|
||||
getRepoAbsolutePath(state.targetRepository),
|
||||
sandbox,
|
||||
);
|
||||
|
||||
|
|
@ -138,9 +138,14 @@ export async function takeAction(
|
|||
logger.info(`Has ${changedFiles.length} changed files. Committing.`, {
|
||||
changedFiles,
|
||||
});
|
||||
branchName = await checkoutBranchAndCommit(config, sandbox, {
|
||||
branchName,
|
||||
});
|
||||
branchName = await checkoutBranchAndCommit(
|
||||
config,
|
||||
state.targetRepository,
|
||||
sandbox,
|
||||
{
|
||||
branchName,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const shouldRouteDiagnoseNode = shouldDiagnoseError(
|
||||
|
|
|
|||
|
|
@ -73,6 +73,12 @@ export const GraphAnnotation = z.object({
|
|||
.string()
|
||||
.optional()
|
||||
.langgraph.reducer((_state, update) => update),
|
||||
/**
|
||||
* The target repository information
|
||||
*/
|
||||
targetRepository: z
|
||||
.custom<TargetRepository>()
|
||||
.langgraph.reducer((_state, update) => update),
|
||||
});
|
||||
|
||||
export type GraphState = z.infer<typeof GraphAnnotation>;
|
||||
|
|
@ -143,25 +149,6 @@ const MODEL_OPTIONS_NO_THINKING = MODEL_OPTIONS.filter(
|
|||
);
|
||||
|
||||
export const GraphConfiguration = z.object({
|
||||
/**
|
||||
* The URL of the repository to clone.
|
||||
*/
|
||||
target_repository: z
|
||||
.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
branch: z.string().optional(),
|
||||
})
|
||||
.langgraph.metadata({
|
||||
x_oap_ui_config: {
|
||||
type: "json",
|
||||
default: `{
|
||||
"owner": "",
|
||||
"repo": "",
|
||||
"branch": ""
|
||||
}`,
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* The model ID to use for the planning step.
|
||||
* This includes initial planning, and rewriting.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { Octokit } from "@octokit/rest";
|
||||
import { CommandResult, Sandbox } from "@e2b/code-interpreter";
|
||||
import { createLogger, LogLevel } from "../logger.js";
|
||||
import { GraphConfig } from "../../types.js";
|
||||
import { GraphConfig, TargetRepository } from "../../types.js";
|
||||
import { TIMEOUT_MS } from "../../constants.js";
|
||||
import { getSandboxErrorFields } from "../sandbox-error-fields.js";
|
||||
|
||||
const logger = createLogger(LogLevel.INFO, "GitUtil");
|
||||
|
||||
export function getRepoAbsolutePath(config: GraphConfig): string {
|
||||
const repoName = config.configurable?.target_repository.repo;
|
||||
export function getRepoAbsolutePath(
|
||||
targetRepository: TargetRepository,
|
||||
): string {
|
||||
const repoName = targetRepository.repo;
|
||||
if (!repoName) {
|
||||
throw new Error("No repository name provided");
|
||||
}
|
||||
|
|
@ -374,13 +376,14 @@ export async function getChangedFilesStatus(
|
|||
|
||||
export async function checkoutBranchAndCommit(
|
||||
config: GraphConfig,
|
||||
targetRepository: TargetRepository,
|
||||
sandbox: Sandbox,
|
||||
options?: {
|
||||
branchName?: string;
|
||||
},
|
||||
): Promise<string> {
|
||||
logger.info("Checking out branch and committing changes...");
|
||||
const absoluteRepoDir = getRepoAbsolutePath(config);
|
||||
const absoluteRepoDir = getRepoAbsolutePath(targetRepository);
|
||||
const branchName = options?.branchName || getBranchName(config);
|
||||
|
||||
await checkoutBranch(absoluteRepoDir, branchName, sandbox);
|
||||
|
|
|
|||
|
|
@ -89,13 +89,6 @@ export default function useInterruptedActions({
|
|||
resume: response,
|
||||
},
|
||||
config: {
|
||||
// TODO: Make configurable (just that target_repository object) & recursion limit in the UI.
|
||||
configurable: {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
},
|
||||
recursion_limit: 400,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -215,8 +215,16 @@ export function Thread() {
|
|||
const context =
|
||||
Object.keys(artifactContext).length > 0 ? artifactContext : undefined;
|
||||
|
||||
const targetRepository = {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
};
|
||||
stream.submit(
|
||||
{ messages: [...toolMessages, newHumanMessage], context },
|
||||
{
|
||||
messages: [...toolMessages, newHumanMessage],
|
||||
context,
|
||||
targetRepository,
|
||||
},
|
||||
{
|
||||
streamMode: ["values"],
|
||||
optimisticValues: (prev) => ({
|
||||
|
|
@ -229,13 +237,6 @@ export function Thread() {
|
|||
],
|
||||
}),
|
||||
config: {
|
||||
// TODO: Make configurable (just that target_repository object) & recursion limit in the UI.
|
||||
configurable: {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
},
|
||||
recursion_limit: 400,
|
||||
},
|
||||
},
|
||||
|
|
@ -255,13 +256,6 @@ export function Thread() {
|
|||
checkpoint: parentCheckpoint,
|
||||
streamMode: ["values"],
|
||||
config: {
|
||||
// TODO: Make configurable (just that target_repository object) & recursion limit in the UI.
|
||||
configurable: {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
},
|
||||
recursion_limit: 400,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -68,13 +68,6 @@ export function HumanMessage({
|
|||
};
|
||||
},
|
||||
config: {
|
||||
// TODO: Make configurable (just that target_repository object) & recursion limit in the UI.
|
||||
configurable: {
|
||||
target_repository: {
|
||||
owner: "langchain-ai",
|
||||
repo: "open-swe",
|
||||
},
|
||||
},
|
||||
recursion_limit: 400,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ import { TooltipIconButton } from "@/components/thread/tooltip-icon-button";
|
|||
import { Copy, CopyCheck } from "lucide-react";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export type StateType = { messages: Message[]; ui?: UIMessage[] };
|
||||
type TargetRepository = { owner: string; repo: string };
|
||||
export type StateType = {
|
||||
messages: Message[];
|
||||
ui?: UIMessage[];
|
||||
targetRepository?: TargetRepository;
|
||||
};
|
||||
|
||||
const useTypedStream = useStream<
|
||||
StateType,
|
||||
|
|
@ -24,6 +29,7 @@ const useTypedStream = useStream<
|
|||
messages?: Message[] | Message | string;
|
||||
ui?: (UIMessage | RemoveUIMessage)[] | UIMessage | RemoveUIMessage;
|
||||
context?: Record<string, unknown>;
|
||||
targetRepository?: TargetRepository;
|
||||
};
|
||||
CustomEventType: UIMessage | RemoveUIMessage;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue