feat: Tsconfig tool and typescript template (#660)

* feat: Tsconfig tool and typescript template

* cr
This commit is contained in:
Brace Sproul 2025-08-04 14:59:36 -07:00 • committed by GitHub
parent 700bbf20f5
commit 71e6094421
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 180 additions and 14 deletions

View file

@ -19,6 +19,7 @@ import {
createUpdatePlanToolFields, createUpdatePlanToolFields,
createGetURLContentTool, createGetURLContentTool,
createSearchDocumentForTool, createSearchDocumentForTool,
createWriteDefaultTsConfigTool,
} from "../../../../tools/index.js"; } from "../../../../tools/index.js";
import { formatPlanPrompt } from "../../../../utils/plan-prompt.js"; import { formatPlanPrompt } from "../../../../utils/plan-prompt.js";
import { stopSandbox } from "../../../../utils/sandbox.js"; import { stopSandbox } from "../../../../utils/sandbox.js";
@ -181,6 +182,7 @@ async function createToolsAndPrompt(
createInstallDependenciesTool(state, config), createInstallDependenciesTool(state, config),
createMarkTaskCompletedToolFields(), createMarkTaskCompletedToolFields(),
createSearchDocumentForTool(state, config), createSearchDocumentForTool(state, config),
createWriteDefaultTsConfigTool(state, config),
...mcpTools, ...mcpTools,
]; ];

View file

@ -7,6 +7,7 @@ import {
createTextEditorTool, createTextEditorTool,
createShellTool, createShellTool,
createSearchDocumentForTool, createSearchDocumentForTool,
createWriteDefaultTsConfigTool,
} from "../../../tools/index.js"; } from "../../../tools/index.js";
import { import {
GraphState, GraphState,
@ -59,11 +60,16 @@ export async function takeAction(
const getURLContentTool = createGetURLContentTool(state); const getURLContentTool = createGetURLContentTool(state);
const searchDocumentForTool = createSearchDocumentForTool(state, config); const searchDocumentForTool = createSearchDocumentForTool(state, config);
const mcpTools = await getMcpTools(config); const mcpTools = await getMcpTools(config);
const writeDefaultTsConfigTool = createWriteDefaultTsConfigTool(
state,
config,
);
const higherContextLimitToolNames = [ const higherContextLimitToolNames = [
...mcpTools.map((t) => t.name), ...mcpTools.map((t) => t.name),
getURLContentTool.name, getURLContentTool.name,
searchDocumentForTool.name, searchDocumentForTool.name,
writeDefaultTsConfigTool.name,
]; ];
const allTools = [ const allTools = [
@ -74,6 +80,7 @@ export async function takeAction(
applyPatchTool, applyPatchTool,
getURLContentTool, getURLContentTool,
searchDocumentForTool, searchDocumentForTool,
writeDefaultTsConfigTool,
...mcpTools, ...mcpTools,
]; ];
const toolsMap = Object.fromEntries( const toolsMap = Object.fromEntries(

View file

@ -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<GraphState, "sandboxSessionId" | "targetRepository">,
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;
}

View file

@ -10,4 +10,4 @@ export {
} from "@open-swe/shared/open-swe/tools"; } from "@open-swe/shared/open-swe/tools";
export * from "./grep.js"; export * from "./grep.js";
export * from "./install-dependencies.js"; export * from "./install-dependencies.js";
export * from "./planner-notes.js"; export * from "./default-tsconfig.js";

View file

@ -1,5 +1,6 @@
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; 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. 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 (
<Card
onClick={() => 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"
>
<CardHeader className="px-3">
<CardTitle className="text-foreground text-sm">
Generate Agent Rules
</CardTitle>
<CardDescription className="text-muted-foreground text-xs">
Generate an AGENTS.md file for the repository.
</CardDescription>
</CardHeader>
</Card>
);
}
function RepositoryTemplateQuickAction(props: {
owner: string;
repo: string;
title: string;
description: string;
}) {
const repoUrl = `https://github.com/${props.owner}/${props.repo}`;
return (
<a
href={repoUrl}
target="_blank"
rel="noopener noreferrer"
>
<Card 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">
<CardHeader className="px-3">
<CardTitle className="text-foreground text-sm">
{props.title}
<p className="text-foreground/80 mt-1 flex items-center gap-1 font-mono text-xs">
<GitHubSVG
width="12"
height="12"
className="flex-shrink-0"
/>
{props.owner}/{props.repo}
</p>
</CardTitle>
<CardDescription className="text-muted-foreground text-xs">
{props.description}
</CardDescription>
</CardHeader>
</Card>
</a>
);
}
interface QuickActionsProps { interface QuickActionsProps {
setQuickActionPrompt: Dispatch<SetStateAction<string>>; setQuickActionPrompt: Dispatch<SetStateAction<string>>;
} }
@ -69,19 +125,15 @@ export function QuickActions({ setQuickActionPrompt }: QuickActionsProps) {
<DevReadmePromptQuickAction <DevReadmePromptQuickAction
setQuickActionPrompt={setQuickActionPrompt} setQuickActionPrompt={setQuickActionPrompt}
/> />
<Card <AgentsMarkdownQuickAction
onClick={() => setQuickActionPrompt(GENERATE_RULES_PROMPT)} setQuickActionPrompt={setQuickActionPrompt}
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" />
> <RepositoryTemplateQuickAction
<CardHeader className="px-3"> owner="bracesproul"
<CardTitle className="text-foreground text-sm"> repo="typescript-template"
Generate Agent Rules title="Clone a TypeScript Template"
</CardTitle> description="Bootstrap a new TypeScript project with pre-configured build tools, linting, testing setup, etc."
<CardDescription className="text-muted-foreground text-xs"> />
Generate an AGENTS.md file for the repository.
</CardDescription>
</CardHeader>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -579,3 +579,25 @@ export function createViewToolFields(
schema: viewSchema, 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,
};
}