From 71e6094421415876ec95cd08ab8f3a74ef401a4f Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 4 Aug 2025 14:59:36 -0700 Subject: [PATCH] feat: Tsconfig tool and typescript template (#660) * feat: Tsconfig tool and typescript template * cr --- .../nodes/generate-message/index.ts | 2 + .../graphs/programmer/nodes/take-action.ts | 7 ++ apps/open-swe/src/tools/default-tsconfig.ts | 83 +++++++++++++++++++ apps/open-swe/src/tools/index.ts | 2 +- apps/open-swe/src/tools/planner-notes.ts | 0 apps/web/src/components/v2/quick-actions.tsx | 78 ++++++++++++++--- packages/shared/src/open-swe/tools.ts | 22 +++++ 7 files changed, 180 insertions(+), 14 deletions(-) create mode 100644 apps/open-swe/src/tools/default-tsconfig.ts delete mode 100644 apps/open-swe/src/tools/planner-notes.ts diff --git a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts index f37e04ff..9c28bc24 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/generate-message/index.ts @@ -19,6 +19,7 @@ import { createUpdatePlanToolFields, createGetURLContentTool, createSearchDocumentForTool, + createWriteDefaultTsConfigTool, } from "../../../../tools/index.js"; import { formatPlanPrompt } from "../../../../utils/plan-prompt.js"; import { stopSandbox } from "../../../../utils/sandbox.js"; @@ -181,6 +182,7 @@ async function createToolsAndPrompt( createInstallDependenciesTool(state, config), createMarkTaskCompletedToolFields(), createSearchDocumentForTool(state, config), + createWriteDefaultTsConfigTool(state, config), ...mcpTools, ]; diff --git a/apps/open-swe/src/graphs/programmer/nodes/take-action.ts b/apps/open-swe/src/graphs/programmer/nodes/take-action.ts index 3eb74391..5d67f219 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/take-action.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/take-action.ts @@ -7,6 +7,7 @@ import { createTextEditorTool, createShellTool, createSearchDocumentForTool, + createWriteDefaultTsConfigTool, } from "../../../tools/index.js"; import { GraphState, @@ -59,11 +60,16 @@ export async function takeAction( const getURLContentTool = createGetURLContentTool(state); const searchDocumentForTool = createSearchDocumentForTool(state, config); const mcpTools = await getMcpTools(config); + const writeDefaultTsConfigTool = createWriteDefaultTsConfigTool( + state, + config, + ); const higherContextLimitToolNames = [ ...mcpTools.map((t) => t.name), getURLContentTool.name, searchDocumentForTool.name, + writeDefaultTsConfigTool.name, ]; const allTools = [ @@ -74,6 +80,7 @@ export async function takeAction( applyPatchTool, getURLContentTool, searchDocumentForTool, + writeDefaultTsConfigTool, ...mcpTools, ]; const toolsMap = Object.fromEntries( diff --git a/apps/open-swe/src/tools/default-tsconfig.ts b/apps/open-swe/src/tools/default-tsconfig.ts new file mode 100644 index 00000000..d7637f02 --- /dev/null +++ b/apps/open-swe/src/tools/default-tsconfig.ts @@ -0,0 +1,83 @@ +import path from "path"; +import { tool } from "@langchain/core/tools"; +import { createWriteDefaultTsConfigToolFields } from "@open-swe/shared/open-swe/tools"; +import { GraphConfig, GraphState } from "@open-swe/shared/open-swe/types"; +import { createShellExecutor } from "../utils/shell-executor/index.js"; +import { TIMEOUT_SEC } from "@open-swe/shared/constants"; +import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js"; + +const DEFAULT_TS_CONFIG = { + extends: "@tsconfig/recommended", + compilerOptions: { + target: "ES2021", + module: "NodeNext", + lib: ["ES2023"], + moduleResolution: "nodenext", + esModuleInterop: true, + noImplicitReturns: true, + declaration: true, + noFallthroughCasesInSwitch: true, + noUnusedLocals: true, + noUnusedParameters: true, + useDefineForClassFields: true, + strictPropertyInitialization: false, + allowJs: true, + strict: true, + strictFunctionTypes: false, + outDir: "dist", + types: ["node"], + resolveJsonModule: true, + }, + include: ["**/*.ts"], + exclude: ["node_modules", "dist"], +}; + +export function createWriteDefaultTsConfigTool( + state: Pick, + config: GraphConfig, +) { + const writeDefaultTsConfigTool = tool( + async (input): Promise<{ result: string; status: "success" | "error" }> => { + const { workdir } = input; + const executor = createShellExecutor(config); + const tsConfigFileName = "tsconfig.json"; + + try { + const response = await executor.executeCommand({ + command: `echo '${JSON.stringify(DEFAULT_TS_CONFIG)}' > ${tsConfigFileName}`, + workdir, + timeout: TIMEOUT_SEC, + }); + if (response.exitCode !== 0) { + throw new Error( + `Failed to write default tsconfig.json. Exit code: ${response.exitCode}\nError: ${response.result}`, + ); + } + + const destinationPath = path.join(workdir, tsConfigFileName); + return { + result: `Successfully wrote to tsconfig.json to ${destinationPath}`, + status: "success", + }; + } catch (error) { + const errorFields = getSandboxErrorFields(error); + if (errorFields) { + return { + result: `Error: ${errorFields.result ?? errorFields.artifacts?.stdout}`, + status: "error", + }; + } + + const errorString = + error instanceof Error ? error.message : String(error); + return { + result: `Error: ${errorString}`, + status: "error", + }; + } + }, + createWriteDefaultTsConfigToolFields(state.targetRepository), + ); + + return writeDefaultTsConfigTool; +} diff --git a/apps/open-swe/src/tools/index.ts b/apps/open-swe/src/tools/index.ts index a9940253..d1051631 100644 --- a/apps/open-swe/src/tools/index.ts +++ b/apps/open-swe/src/tools/index.ts @@ -10,4 +10,4 @@ export { } from "@open-swe/shared/open-swe/tools"; export * from "./grep.js"; export * from "./install-dependencies.js"; -export * from "./planner-notes.js"; +export * from "./default-tsconfig.js"; diff --git a/apps/open-swe/src/tools/planner-notes.ts b/apps/open-swe/src/tools/planner-notes.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/web/src/components/v2/quick-actions.tsx b/apps/web/src/components/v2/quick-actions.tsx index 4e01380a..a3f7e7c0 100644 --- a/apps/web/src/components/v2/quick-actions.tsx +++ b/apps/web/src/components/v2/quick-actions.tsx @@ -1,5 +1,6 @@ import { Dispatch, SetStateAction } from "react"; import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; +import { GitHubSVG } from "../icons/github"; const GENERATE_RULES_PROMPT = `You're given a task to write a collection of rules, context and guidelines on the repository you're provided. Please gather context on the following categories, then write an \`AGENTS.md\` file in the root of the repository. @@ -55,6 +56,61 @@ function DevReadmePromptQuickAction({ ); } +function AgentsMarkdownQuickAction({ + setQuickActionPrompt, +}: QuickActionsProps) { + return ( + setQuickActionPrompt(GENERATE_RULES_PROMPT)} + className="border-border bg-card hover:bg-muted/30 dark:hover:bg-muted/20 hover:shadow-primary/2 cursor-pointer py-3 transition-all duration-200 hover:shadow-sm" + > + + + Generate Agent Rules + + + Generate an AGENTS.md file for the repository. + + + + ); +} + +function RepositoryTemplateQuickAction(props: { + owner: string; + repo: string; + title: string; + description: string; +}) { + const repoUrl = `https://github.com/${props.owner}/${props.repo}`; + return ( + + + + + {props.title} +

+ + {props.owner}/{props.repo} +

+
+ + {props.description} + +
+
+
+ ); +} + interface QuickActionsProps { setQuickActionPrompt: Dispatch>; } @@ -69,19 +125,15 @@ export function QuickActions({ setQuickActionPrompt }: QuickActionsProps) { - setQuickActionPrompt(GENERATE_RULES_PROMPT)} - className="border-border bg-card hover:bg-muted/30 dark:hover:bg-muted/20 hover:shadow-primary/2 cursor-pointer py-3 transition-all duration-200 hover:shadow-sm" - > - - - Generate Agent Rules - - - Generate an AGENTS.md file for the repository. - - - + + ); diff --git a/packages/shared/src/open-swe/tools.ts b/packages/shared/src/open-swe/tools.ts index 8bb22f90..14a0c813 100644 --- a/packages/shared/src/open-swe/tools.ts +++ b/packages/shared/src/open-swe/tools.ts @@ -579,3 +579,25 @@ export function createViewToolFields( schema: viewSchema, }; } + +export function createWriteDefaultTsConfigToolFields( + targetRepository: TargetRepository, +) { + const repoRoot = getRepoAbsolutePath(targetRepository); + + const writeDefaultTsConfigToolSchema = z.object({ + workdir: z + .string() + .default(repoRoot) + .describe( + `The directory which the tsconfig.json file will be written to. The default value is the root of the repository: \`${repoRoot}\`.`, + ), + }); + + return { + name: "write_default_tsconfig", + description: + "Writes a default tsconfig.json file to the specified directory. This should ONLY be called when creating a new TypeScript project.", + schema: writeDefaultTsConfigToolSchema, + }; +}