From ff23ea49a8bca10446ad48e1bd63ca2cd5635eb2 Mon Sep 17 00:00:00 2001 From: Harrison Chase Date: Thu, 29 May 2025 20:07:28 -0400 Subject: [PATCH] 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 --- apps/open-swe/scripts/run-e2e.ts | 24 +++--------------- apps/open-swe/scripts/run-from-plan.ts | 22 ++++++---------- apps/open-swe/src/nodes/generate-message.ts | 6 ++--- apps/open-swe/src/nodes/initialize.ts | 12 ++++----- apps/open-swe/src/nodes/open-pr.ts | 15 +++++++---- apps/open-swe/src/nodes/take-action.ts | 13 +++++++--- apps/open-swe/src/types.ts | 25 +++++-------------- apps/open-swe/src/utils/git/index.ts | 11 +++++--- .../hooks/use-interrupted-actions.tsx | 7 ------ apps/web/src/components/thread/index.tsx | 24 +++++++----------- .../src/components/thread/messages/human.tsx | 7 ------ apps/web/src/providers/Stream.tsx | 8 +++++- 12 files changed, 68 insertions(+), 106 deletions(-) diff --git a/apps/open-swe/scripts/run-e2e.ts b/apps/open-swe/scripts/run-e2e.ts index 8015b903..d553ee9f 100644 --- a/apps/open-swe/scripts/run-e2e.ts +++ b/apps/open-swe/scripts/run-e2e.ts @@ -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, diff --git a/apps/open-swe/scripts/run-from-plan.ts b/apps/open-swe/scripts/run-from-plan.ts index e2deff2f..a5d1e42d 100644 --- a/apps/open-swe/scripts/run-from-plan.ts +++ b/apps/open-swe/scripts/run-from-plan.ts @@ -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", diff --git a/apps/open-swe/src/nodes/generate-message.ts b/apps/open-swe/src/nodes/generate-message.ts index fdc71957..fe2505a9 100644 --- a/apps/open-swe/src/nodes/generate-message.ts +++ b/apps/open-swe/src/nodes/generate-message.ts @@ -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, ]); diff --git a/apps/open-swe/src/nodes/initialize.ts b/apps/open-swe/src/nodes/initialize.ts index 64c68be0..ab8f36da 100644 --- a/apps/open-swe/src/nodes/initialize.ts +++ b/apps/open-swe/src/nodes/initialize.ts @@ -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, }; } diff --git a/apps/open-swe/src/nodes/open-pr.ts b/apps/open-swe/src/nodes/open-pr.ts index ef16903c..7be9313c 100644 --- a/apps/open-swe/src/nodes/open-pr.ts +++ b/apps/open-swe/src/nodes/open-pr.ts @@ -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); diff --git a/apps/open-swe/src/nodes/take-action.ts b/apps/open-swe/src/nodes/take-action.ts index bb86de5c..340f0518 100644 --- a/apps/open-swe/src/nodes/take-action.ts +++ b/apps/open-swe/src/nodes/take-action.ts @@ -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( diff --git a/apps/open-swe/src/types.ts b/apps/open-swe/src/types.ts index 9b80af41..3689d2f1 100644 --- a/apps/open-swe/src/types.ts +++ b/apps/open-swe/src/types.ts @@ -73,6 +73,12 @@ export const GraphAnnotation = z.object({ .string() .optional() .langgraph.reducer((_state, update) => update), + /** + * The target repository information + */ + targetRepository: z + .custom() + .langgraph.reducer((_state, update) => update), }); export type GraphState = z.infer; @@ -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. diff --git a/apps/open-swe/src/utils/git/index.ts b/apps/open-swe/src/utils/git/index.ts index 07db05ba..eb6e8767 100644 --- a/apps/open-swe/src/utils/git/index.ts +++ b/apps/open-swe/src/utils/git/index.ts @@ -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 { 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); diff --git a/apps/web/src/components/thread/agent-inbox/hooks/use-interrupted-actions.tsx b/apps/web/src/components/thread/agent-inbox/hooks/use-interrupted-actions.tsx index e5d20103..552dec95 100644 --- a/apps/web/src/components/thread/agent-inbox/hooks/use-interrupted-actions.tsx +++ b/apps/web/src/components/thread/agent-inbox/hooks/use-interrupted-actions.tsx @@ -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, }, }, diff --git a/apps/web/src/components/thread/index.tsx b/apps/web/src/components/thread/index.tsx index 18818401..37ff1846 100644 --- a/apps/web/src/components/thread/index.tsx +++ b/apps/web/src/components/thread/index.tsx @@ -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, }, }); diff --git a/apps/web/src/components/thread/messages/human.tsx b/apps/web/src/components/thread/messages/human.tsx index 877608bb..d0f66013 100644 --- a/apps/web/src/components/thread/messages/human.tsx +++ b/apps/web/src/components/thread/messages/human.tsx @@ -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, }, }, diff --git a/apps/web/src/providers/Stream.tsx b/apps/web/src/providers/Stream.tsx index 2f73a18d..edc7af27 100644 --- a/apps/web/src/providers/Stream.tsx +++ b/apps/web/src/providers/Stream.tsx @@ -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; + targetRepository?: TargetRepository; }; CustomEventType: UIMessage | RemoveUIMessage; }