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,
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,
];

View file

@ -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(

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";
export * from "./grep.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 { 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 (
<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 {
setQuickActionPrompt: Dispatch<SetStateAction<string>>;
}
@ -69,19 +125,15 @@ export function QuickActions({ setQuickActionPrompt }: QuickActionsProps) {
<DevReadmePromptQuickAction
setQuickActionPrompt={setQuickActionPrompt}
/>
<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>
<AgentsMarkdownQuickAction
setQuickActionPrompt={setQuickActionPrompt}
/>
<RepositoryTemplateQuickAction
owner="bracesproul"
repo="typescript-template"
title="Clone a TypeScript Template"
description="Bootstrap a new TypeScript project with pre-configured build tools, linting, testing setup, etc."
/>
</div>
</div>
);

View file

@ -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,
};
}