mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 15:09:09 +00:00
[open-swe] feat: Add find_instances_of tool for comprehensive string search (#350)
* Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * Apply patch * cr * cr * fix * cr --------- Co-authored-by: open-swe-dev[bot] <open-swe-dev@users.noreply.github.com> Co-authored-by: bracesproul <braceasproul@gmail.com>
This commit is contained in:
parent
23f72d0669
commit
77fe0e9a05
8 changed files with 434 additions and 19 deletions
|
|
@ -22,6 +22,7 @@ import { getTaskPlanFromIssue } from "../../../../utils/github/issue-task.js";
|
||||||
import { createRgTool } from "../../../../tools/rg.js";
|
import { createRgTool } from "../../../../tools/rg.js";
|
||||||
import { formatCustomRulesPrompt } from "../../../../utils/custom-rules.js";
|
import { formatCustomRulesPrompt } from "../../../../utils/custom-rules.js";
|
||||||
import { createPlannerNotesTool } from "../../../../tools/planner-notes.js";
|
import { createPlannerNotesTool } from "../../../../tools/planner-notes.js";
|
||||||
|
import { createFindInstancesOfTool } from "../../../../tools/find-instances-of.js";
|
||||||
import { getMcpTools } from "../../../../utils/mcp-client.js";
|
import { getMcpTools } from "../../../../utils/mcp-client.js";
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode");
|
const logger = createLogger(LogLevel.INFO, "GeneratePlanningMessageNode");
|
||||||
|
|
@ -56,6 +57,7 @@ export async function generateAction(
|
||||||
const tools = [
|
const tools = [
|
||||||
createRgTool(state),
|
createRgTool(state),
|
||||||
createShellTool(state),
|
createShellTool(state),
|
||||||
|
createFindInstancesOfTool(state),
|
||||||
createPlannerNotesTool(),
|
createPlannerNotesTool(),
|
||||||
createGetURLContentTool(),
|
createGetURLContentTool(),
|
||||||
...mcpTools,
|
...mcpTools,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
getChangedFilesStatus,
|
getChangedFilesStatus,
|
||||||
stashAndClearChanges,
|
stashAndClearChanges,
|
||||||
} from "../../../utils/github/git.js";
|
} from "../../../utils/github/git.js";
|
||||||
|
import { createFindInstancesOfTool } from "../../../tools/find-instances-of.js";
|
||||||
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||||
import { daytonaClient } from "../../../utils/sandbox.js";
|
import { daytonaClient } from "../../../utils/sandbox.js";
|
||||||
import { createPlannerNotesTool } from "../../../tools/planner-notes.js";
|
import { createPlannerNotesTool } from "../../../tools/planner-notes.js";
|
||||||
|
|
@ -40,8 +41,8 @@ export async function takeActions(
|
||||||
const shellTool = createShellTool(state);
|
const shellTool = createShellTool(state);
|
||||||
const rgTool = createRgTool(state);
|
const rgTool = createRgTool(state);
|
||||||
const plannerNotesTool = createPlannerNotesTool();
|
const plannerNotesTool = createPlannerNotesTool();
|
||||||
|
const findInstancesOfTool = createFindInstancesOfTool(state);
|
||||||
const getURLContentTool = createGetURLContentTool();
|
const getURLContentTool = createGetURLContentTool();
|
||||||
|
|
||||||
const mcpTools = await getMcpTools(config);
|
const mcpTools = await getMcpTools(config);
|
||||||
|
|
||||||
const allTools = [
|
const allTools = [
|
||||||
|
|
@ -49,6 +50,7 @@ export async function takeActions(
|
||||||
rgTool,
|
rgTool,
|
||||||
plannerNotesTool,
|
plannerNotesTool,
|
||||||
getURLContentTool,
|
getURLContentTool,
|
||||||
|
findInstancesOfTool,
|
||||||
...mcpTools,
|
...mcpTools,
|
||||||
];
|
];
|
||||||
const toolsMap = Object.fromEntries(
|
const toolsMap = Object.fromEntries(
|
||||||
|
|
|
||||||
154
apps/open-swe/src/tools/find-instances-of.ts
Normal file
154
apps/open-swe/src/tools/find-instances-of.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
import { tool } from "@langchain/core/tools";
|
||||||
|
import { Sandbox } from "@daytonaio/sdk";
|
||||||
|
import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||||
|
import { getCurrentTaskInput } from "@langchain/langgraph";
|
||||||
|
import { getSandboxErrorFields } from "../utils/sandbox-error-fields.js";
|
||||||
|
import { createLogger, LogLevel } from "../utils/logger.js";
|
||||||
|
import { daytonaClient } from "../utils/sandbox.js";
|
||||||
|
import { TIMEOUT_SEC } from "@open-swe/shared/constants";
|
||||||
|
import { createFindInstancesOfToolFields } from "@open-swe/shared/open-swe/tools";
|
||||||
|
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { wrapScript } from "../utils/wrap-script.js";
|
||||||
|
|
||||||
|
const logger = createLogger(LogLevel.INFO, "FindInstancesOfTool");
|
||||||
|
|
||||||
|
const DEFAULT_ENV = {
|
||||||
|
// Prevents corepack from showing a y/n download prompt which causes the command to hang
|
||||||
|
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createFindInstancesOfTool(
|
||||||
|
state: Pick<GraphState, "sandboxSessionId" | "targetRepository">,
|
||||||
|
) {
|
||||||
|
const findInstancesOfFields = createFindInstancesOfToolFields(
|
||||||
|
state.targetRepository,
|
||||||
|
);
|
||||||
|
const formatFindInstancesOfCommand = (
|
||||||
|
input: z.infer<typeof findInstancesOfFields.schema>,
|
||||||
|
): string[] => {
|
||||||
|
const args = ["rg"];
|
||||||
|
|
||||||
|
// Always include these flags for consistent output
|
||||||
|
args.push("--color", "never", "--line-number", "--heading");
|
||||||
|
|
||||||
|
// Add context lines (3 above and 3 below)
|
||||||
|
args.push("-A", "3", "-B", "3");
|
||||||
|
|
||||||
|
// Handle case sensitivity
|
||||||
|
if (!input.case_sensitive) {
|
||||||
|
args.push("-i");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle word matching
|
||||||
|
if (input.match_word) {
|
||||||
|
args.push("--word-regexp");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle file inclusion/exclusion patterns
|
||||||
|
if (input.exclude_files) {
|
||||||
|
args.push("-g", `!${input.exclude_files}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.include_files) {
|
||||||
|
args.push("-g", input.include_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For literal string matching (not regex)
|
||||||
|
args.push("--fixed-strings");
|
||||||
|
|
||||||
|
// Add the search query as the last argument (ensure it's properly quoted)
|
||||||
|
const formattedQuery = `'${input.query.replace(/^'|'$/g, "")}'`;
|
||||||
|
args.push(formattedQuery);
|
||||||
|
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findInstancesOfTool = tool(
|
||||||
|
async (
|
||||||
|
input: z.infer<typeof findInstancesOfFields.schema>,
|
||||||
|
): Promise<{ result: string; status: "success" | "error" }> => {
|
||||||
|
let sandbox: Sandbox | undefined;
|
||||||
|
try {
|
||||||
|
const state = getCurrentTaskInput<GraphState>();
|
||||||
|
const { sandboxSessionId } = state;
|
||||||
|
if (!sandboxSessionId) {
|
||||||
|
logger.error(
|
||||||
|
"FAILED TO RUN COMMAND: No sandbox session ID provided",
|
||||||
|
{
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
"FAILED TO RUN COMMAND: No sandbox session ID provided",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const repoRoot = getRepoAbsolutePath(state.targetRepository);
|
||||||
|
|
||||||
|
sandbox = await daytonaClient().get(sandboxSessionId);
|
||||||
|
const command = formatFindInstancesOfCommand(input);
|
||||||
|
logger.info("Running find_instances_of command", {
|
||||||
|
command: command.join(" "),
|
||||||
|
repoRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await sandbox.process.executeCommand(
|
||||||
|
wrapScript(command.join(" ")),
|
||||||
|
repoRoot,
|
||||||
|
DEFAULT_ENV,
|
||||||
|
TIMEOUT_SEC,
|
||||||
|
);
|
||||||
|
|
||||||
|
let successResult = response.result;
|
||||||
|
|
||||||
|
if (
|
||||||
|
response.exitCode === 1 ||
|
||||||
|
(response.exitCode === 127 && response.result.startsWith("sh: 1: "))
|
||||||
|
) {
|
||||||
|
logger.info("Exit code 1. no results found", {
|
||||||
|
...response,
|
||||||
|
});
|
||||||
|
successResult = `Exit code 1. No results found.\n\n${response.result}`;
|
||||||
|
} else if (response.exitCode > 1) {
|
||||||
|
logger.error("Failed to run find_instances_of command", {
|
||||||
|
error: response.result,
|
||||||
|
error_result: response,
|
||||||
|
input,
|
||||||
|
});
|
||||||
|
throw new Error("Command failed. Exit code: " + response.exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: successResult,
|
||||||
|
status: "success",
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
const errorFields = getSandboxErrorFields(e);
|
||||||
|
if (errorFields) {
|
||||||
|
logger.error("Failed to run find_instances_of command", {
|
||||||
|
input,
|
||||||
|
error: errorFields,
|
||||||
|
});
|
||||||
|
throw new Error("Command failed. Exit code: " + errorFields.exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
"Failed to run find_instances_of command: " +
|
||||||
|
(e instanceof Error ? e.message : "Unknown error"),
|
||||||
|
{
|
||||||
|
error: e,
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
"FAILED TO RUN FIND_INSTANCES_OF COMMAND: " +
|
||||||
|
(e instanceof Error ? e.message : "Unknown error"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findInstancesOfFields,
|
||||||
|
);
|
||||||
|
|
||||||
|
return findInstancesOfTool;
|
||||||
|
}
|
||||||
|
|
@ -11,13 +11,7 @@ import {
|
||||||
formatRgCommand,
|
formatRgCommand,
|
||||||
} from "@open-swe/shared/open-swe/tools";
|
} from "@open-swe/shared/open-swe/tools";
|
||||||
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
import { getRepoAbsolutePath } from "@open-swe/shared/git";
|
||||||
|
import { wrapScript } from "../utils/wrap-script.js";
|
||||||
const wrapScript = (command: string): string => {
|
|
||||||
return `script --return --quiet -c "$(cat <<'OPEN_SWE_X'
|
|
||||||
${command}
|
|
||||||
OPEN_SWE_X
|
|
||||||
)" /dev/null`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const logger = createLogger(LogLevel.INFO, "RgTool");
|
const logger = createLogger(LogLevel.INFO, "RgTool");
|
||||||
|
|
||||||
|
|
|
||||||
6
apps/open-swe/src/utils/wrap-script.ts
Normal file
6
apps/open-swe/src/utils/wrap-script.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export function wrapScript(command: string): string {
|
||||||
|
return `script --return --quiet -c "$(cat <<'OPEN_SWE_X'
|
||||||
|
${command}
|
||||||
|
OPEN_SWE_X
|
||||||
|
)" /dev/null`;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { JSX, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Terminal,
|
Terminal,
|
||||||
FileCode,
|
FileCode,
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
CloudDownload,
|
CloudDownload,
|
||||||
Search,
|
Search,
|
||||||
Globe,
|
Globe,
|
||||||
|
Hash,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
createApplyPatchToolFields,
|
createApplyPatchToolFields,
|
||||||
|
|
@ -21,10 +22,18 @@ import {
|
||||||
createInstallDependenciesToolFields,
|
createInstallDependenciesToolFields,
|
||||||
createTakePlannerNotesFields,
|
createTakePlannerNotesFields,
|
||||||
createGetURLContentToolFields,
|
createGetURLContentToolFields,
|
||||||
|
createFindInstancesOfToolFields,
|
||||||
formatRgCommand,
|
formatRgCommand,
|
||||||
RipgrepCommand,
|
RipgrepCommand,
|
||||||
} from "@open-swe/shared/open-swe/tools";
|
} from "@open-swe/shared/open-swe/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "../ui/tooltip";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
// Used only for Zod type inference.
|
// Used only for Zod type inference.
|
||||||
const dummyRepo = { owner: "dummy", repo: "dummy" };
|
const dummyRepo = { owner: "dummy", repo: "dummy" };
|
||||||
|
|
@ -40,6 +49,8 @@ const plannerNotesTool = createTakePlannerNotesFields();
|
||||||
type PlannerNotesToolArgs = z.infer<typeof plannerNotesTool.schema>;
|
type PlannerNotesToolArgs = z.infer<typeof plannerNotesTool.schema>;
|
||||||
const getURLContentTool = createGetURLContentToolFields();
|
const getURLContentTool = createGetURLContentToolFields();
|
||||||
type GetURLContentToolArgs = z.infer<typeof getURLContentTool.schema>;
|
type GetURLContentToolArgs = z.infer<typeof getURLContentTool.schema>;
|
||||||
|
const findInstancesOfTool = createFindInstancesOfToolFields(dummyRepo);
|
||||||
|
type FindInstancesOfToolArgs = z.infer<typeof findInstancesOfTool.schema>;
|
||||||
|
|
||||||
// Common props for all action types
|
// Common props for all action types
|
||||||
type BaseActionProps = {
|
type BaseActionProps = {
|
||||||
|
|
@ -91,6 +102,13 @@ type GetURLContentActionProps = BaseActionProps &
|
||||||
output?: string;
|
output?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type FindInstancesOfActionProps = BaseActionProps &
|
||||||
|
Partial<FindInstancesOfToolArgs> & {
|
||||||
|
actionType: "find_instances_of";
|
||||||
|
output?: string;
|
||||||
|
errorCode?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ActionItemProps =
|
export type ActionItemProps =
|
||||||
| (BaseActionProps & { status: "loading" })
|
| (BaseActionProps & { status: "loading" })
|
||||||
| ShellActionProps
|
| ShellActionProps
|
||||||
|
|
@ -98,7 +116,8 @@ export type ActionItemProps =
|
||||||
| RgActionProps
|
| RgActionProps
|
||||||
| InstallDependenciesActionProps
|
| InstallDependenciesActionProps
|
||||||
| PlannerNotesActionProps
|
| PlannerNotesActionProps
|
||||||
| GetURLContentActionProps;
|
| GetURLContentActionProps
|
||||||
|
| FindInstancesOfActionProps;
|
||||||
|
|
||||||
export type ActionStepProps = {
|
export type ActionStepProps = {
|
||||||
actions: ActionItemProps[];
|
actions: ActionItemProps[];
|
||||||
|
|
@ -113,8 +132,88 @@ const ACTION_GENERATING_TEXT_MAP = {
|
||||||
[installDependenciesTool.name]: "Installing dependencies...",
|
[installDependenciesTool.name]: "Installing dependencies...",
|
||||||
[plannerNotesTool.name]: "Saving notes...",
|
[plannerNotesTool.name]: "Saving notes...",
|
||||||
[getURLContentTool.name]: "Fetching URL content...",
|
[getURLContentTool.name]: "Fetching URL content...",
|
||||||
|
[findInstancesOfTool.name]: "Finding instances...",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function ToolIconWithTooltip({
|
||||||
|
toolNamePretty,
|
||||||
|
icon,
|
||||||
|
}: {
|
||||||
|
toolNamePretty: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>{icon}</TooltipTrigger>
|
||||||
|
<TooltipContent>{toolNamePretty}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MatchCaseIcon({ matchCase }: { matchCase: boolean }) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
className={cn(
|
||||||
|
"rounded-sm border border-gray-300 px-1 py-[2px] text-xs dark:border-gray-600",
|
||||||
|
matchCase
|
||||||
|
? "border-blue-500 bg-blue-500/80 text-white"
|
||||||
|
: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p className="font-mono">Aa</p>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Match case {matchCase ? "on" : "off"}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MatchWholeWordIcon({ matchWholeWord }: { matchWholeWord: boolean }) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
className={cn(
|
||||||
|
"rounded-sm border border-gray-300 px-1 py-[2px] text-xs dark:border-gray-600",
|
||||||
|
matchWholeWord
|
||||||
|
? "border-blue-500 bg-blue-500/80 text-white"
|
||||||
|
: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="relative inline-block px-0.5">
|
||||||
|
<p className="relative z-10 m-0 text-center font-mono">ab</p>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute bottom-0 left-0 h-1/5 w-[1px]",
|
||||||
|
matchWholeWord ? "bg-white" : "bg-gray-600 dark:bg-gray-300",
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute right-0 bottom-0 left-0 h-[1px] w-full",
|
||||||
|
matchWholeWord ? "bg-white" : "bg-gray-600 dark:bg-gray-300",
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute right-0 bottom-0 h-1/5 w-[1px]",
|
||||||
|
matchWholeWord ? "bg-white" : "bg-gray-600 dark:bg-gray-300",
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
Match whole word {matchWholeWord ? "on" : "off"}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ActionItem(props: ActionItemProps) {
|
function ActionItem(props: ActionItemProps) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
|
@ -159,6 +258,8 @@ function ActionItem(props: ActionItemProps) {
|
||||||
return props.success
|
return props.success
|
||||||
? "URL content fetched"
|
? "URL content fetched"
|
||||||
: "Failed to fetch URL content";
|
: "Failed to fetch URL content";
|
||||||
|
} else if (props.actionType === "find_instances_of") {
|
||||||
|
return props.success ? "Search completed" : "Search failed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,7 +274,8 @@ function ActionItem(props: ActionItemProps) {
|
||||||
props.actionType === "shell" ||
|
props.actionType === "shell" ||
|
||||||
props.actionType === "rg" ||
|
props.actionType === "rg" ||
|
||||||
props.actionType === "install_dependencies" ||
|
props.actionType === "install_dependencies" ||
|
||||||
props.actionType === "get_url_content"
|
props.actionType === "get_url_content" ||
|
||||||
|
props.actionType === "find_instances_of"
|
||||||
) {
|
) {
|
||||||
return !!props.output;
|
return !!props.output;
|
||||||
} else if (props.actionType === "apply-patch") {
|
} else if (props.actionType === "apply-patch") {
|
||||||
|
|
@ -187,23 +289,66 @@ function ActionItem(props: ActionItemProps) {
|
||||||
|
|
||||||
// Render the header icon based on action type
|
// Render the header icon based on action type
|
||||||
const renderHeaderIcon = () => {
|
const renderHeaderIcon = () => {
|
||||||
|
const defaultIconStyling = "text-muted-foreground mr-2 size-3.5";
|
||||||
if (props.status === "loading" || !("actionType" in props)) {
|
if (props.status === "loading" || !("actionType" in props)) {
|
||||||
// In loading state, we don't know the type yet, use a generic icon
|
// In loading state, we don't know the type yet, use a generic icon
|
||||||
return <Loader2 className="text-muted-foreground mr-2 size-3.5" />;
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Loading"
|
||||||
|
icon={<Loader2 className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.actionType === "planner_notes") {
|
if (props.actionType === "planner_notes") {
|
||||||
return <FileText className="text-muted-foreground mr-2 size-3.5" />;
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Planner Notes"
|
||||||
|
icon={<FileText className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (props.actionType === "install_dependencies") {
|
} else if (props.actionType === "install_dependencies") {
|
||||||
return <CloudDownload className="text-muted-foreground mr-2 size-3.5" />;
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Install Dependencies"
|
||||||
|
icon={<CloudDownload className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (props.actionType === "apply-patch") {
|
} else if (props.actionType === "apply-patch") {
|
||||||
return <FileCode className="text-muted-foreground mr-2 size-3.5" />;
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Apply Patch"
|
||||||
|
icon={<FileCode className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (props.actionType === "rg") {
|
} else if (props.actionType === "rg") {
|
||||||
return <Search className="text-muted-foreground mr-2 size-3.5" />;
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Ripgrep (rg)"
|
||||||
|
icon={<Search className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (props.actionType === "get_url_content") {
|
} else if (props.actionType === "get_url_content") {
|
||||||
return <Globe className="text-muted-foreground mr-2 size-3.5" />;
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Get URL Contents"
|
||||||
|
icon={<Globe className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (props.actionType === "find_instances_of") {
|
||||||
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Find Instances of"
|
||||||
|
icon={<Hash className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <Terminal className="text-muted-foreground mr-2 size-3.5" />;
|
return (
|
||||||
|
<ToolIconWithTooltip
|
||||||
|
toolNamePretty="Tool Call"
|
||||||
|
icon={<Terminal className={cn(defaultIconStyling)} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -237,6 +382,32 @@ function ActionItem(props: ActionItemProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.actionType === "find_instances_of") {
|
||||||
|
return (
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<code className="text-foreground/80 text-xs font-normal">
|
||||||
|
{props.query}
|
||||||
|
</code>
|
||||||
|
<div className="bg-border h-4 w-[1px] dark:bg-white"></div>
|
||||||
|
<MatchCaseIcon matchCase={!!props.case_sensitive} />
|
||||||
|
<MatchWholeWordIcon matchWholeWord={!!props.match_word} />
|
||||||
|
</div>
|
||||||
|
{(props.include_files || props.exclude_files) && (
|
||||||
|
<div className="text-muted-foreground mt-0.5 text-xs font-normal">
|
||||||
|
{props.include_files && (
|
||||||
|
<span>Include: {props.include_files}</span>
|
||||||
|
)}
|
||||||
|
{props.include_files && props.exclude_files && <span> | </span>}
|
||||||
|
{props.exclude_files && (
|
||||||
|
<span>Exclude: {props.exclude_files}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
props.actionType === "shell" ||
|
props.actionType === "shell" ||
|
||||||
props.actionType === "install_dependencies"
|
props.actionType === "install_dependencies"
|
||||||
|
|
@ -305,6 +476,7 @@ function ActionItem(props: ActionItemProps) {
|
||||||
if (
|
if (
|
||||||
(props.actionType === "shell" ||
|
(props.actionType === "shell" ||
|
||||||
props.actionType === "rg" ||
|
props.actionType === "rg" ||
|
||||||
|
props.actionType === "find_instances_of" ||
|
||||||
props.actionType === "install_dependencies") &&
|
props.actionType === "install_dependencies") &&
|
||||||
props.output
|
props.output
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import {
|
||||||
createTakePlannerNotesFields,
|
createTakePlannerNotesFields,
|
||||||
createDiagnoseErrorToolFields,
|
createDiagnoseErrorToolFields,
|
||||||
createGetURLContentToolFields,
|
createGetURLContentToolFields,
|
||||||
|
createFindInstancesOfToolFields,
|
||||||
} from "@open-swe/shared/open-swe/tools";
|
} from "@open-swe/shared/open-swe/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages";
|
import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages";
|
||||||
|
|
@ -63,6 +64,9 @@ type DiagnoseErrorToolArgs = z.infer<typeof diagnoseErrorTool.schema>;
|
||||||
const getURLContentTool = createGetURLContentToolFields();
|
const getURLContentTool = createGetURLContentToolFields();
|
||||||
type GetURLContentToolArgs = z.infer<typeof getURLContentTool.schema>;
|
type GetURLContentToolArgs = z.infer<typeof getURLContentTool.schema>;
|
||||||
|
|
||||||
|
const findInstancesOfTool = createFindInstancesOfToolFields(dummyRepo);
|
||||||
|
type FindInstancesOfToolArgs = z.infer<typeof findInstancesOfTool.schema>;
|
||||||
|
|
||||||
function CustomComponent({
|
function CustomComponent({
|
||||||
message,
|
message,
|
||||||
thread,
|
thread,
|
||||||
|
|
@ -199,6 +203,24 @@ export function mapToolMessageToActionStepProps(
|
||||||
output: getContentString(message.content),
|
output: getContentString(message.content),
|
||||||
reasoningText,
|
reasoningText,
|
||||||
};
|
};
|
||||||
|
} else if (toolCall?.name === findInstancesOfTool.name) {
|
||||||
|
const args = toolCall.args as FindInstancesOfToolArgs;
|
||||||
|
// case_sensitive and match_word both default to true.
|
||||||
|
const caseSensitive =
|
||||||
|
args.case_sensitive === undefined ? true : args.case_sensitive;
|
||||||
|
const matchWord = args.match_word === undefined ? true : args.match_word;
|
||||||
|
return {
|
||||||
|
actionType: "find_instances_of",
|
||||||
|
status,
|
||||||
|
success,
|
||||||
|
query: args.query || "",
|
||||||
|
case_sensitive: caseSensitive,
|
||||||
|
match_word: matchWord,
|
||||||
|
include_files: args.include_files,
|
||||||
|
exclude_files: args.exclude_files,
|
||||||
|
output: getContentString(message.content),
|
||||||
|
reasoningText,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
|
|
@ -264,7 +286,8 @@ export function AssistantMessage({
|
||||||
tc.name === rgTool.name ||
|
tc.name === rgTool.name ||
|
||||||
tc.name === installDependenciesTool.name ||
|
tc.name === installDependenciesTool.name ||
|
||||||
tc.name === plannerNotesTool.name ||
|
tc.name === plannerNotesTool.name ||
|
||||||
tc.name === getURLContentTool.name,
|
tc.name === getURLContentTool.name ||
|
||||||
|
tc.name === findInstancesOfTool.name,
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
|
@ -420,6 +443,23 @@ export function AssistantMessage({
|
||||||
url: args?.url || "",
|
url: args?.url || "",
|
||||||
output: "",
|
output: "",
|
||||||
} as ActionItemProps;
|
} as ActionItemProps;
|
||||||
|
} else if (toolCall.name === findInstancesOfTool.name) {
|
||||||
|
const args = toolCall.args as FindInstancesOfToolArgs;
|
||||||
|
// case_sensitive and match_word both default to true.
|
||||||
|
const caseSensitive =
|
||||||
|
args.case_sensitive === undefined ? true : args.case_sensitive;
|
||||||
|
const matchWord =
|
||||||
|
args.match_word === undefined ? true : args.match_word;
|
||||||
|
return {
|
||||||
|
actionType: "find_instances_of",
|
||||||
|
status: "generating",
|
||||||
|
query: args?.query || "",
|
||||||
|
case_sensitive: caseSensitive,
|
||||||
|
match_word: matchWord,
|
||||||
|
include_files: args?.include_files,
|
||||||
|
exclude_files: args?.exclude_files,
|
||||||
|
output: "",
|
||||||
|
} as ActionItemProps;
|
||||||
} else {
|
} else {
|
||||||
if (isShellTool) {
|
if (isShellTool) {
|
||||||
const args = toolCall.args as ShellToolArgs;
|
const args = toolCall.args as ShellToolArgs;
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,51 @@ export function formatRgCommand(cmd: RipgrepCommand): string[] {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createFindInstancesOfToolFields(
|
||||||
|
targetRepository: TargetRepository,
|
||||||
|
) {
|
||||||
|
const repoRoot = getRepoAbsolutePath(targetRepository);
|
||||||
|
const findInstancesOfSchema = z.object({
|
||||||
|
query: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
"The query/keyword to search for. This should be a literal string, not a regex.",
|
||||||
|
),
|
||||||
|
|
||||||
|
case_sensitive: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.default(true)
|
||||||
|
.describe(
|
||||||
|
"Whether or not to make the query search case sensitive. Defaults to true",
|
||||||
|
),
|
||||||
|
|
||||||
|
match_word: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.default(true)
|
||||||
|
.describe(
|
||||||
|
"Whether or not to only show results which match the exact keyword. Defaults to true",
|
||||||
|
),
|
||||||
|
|
||||||
|
exclude_files: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("Glob pattern of files to exclude"),
|
||||||
|
|
||||||
|
include_files: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe("Glob pattern of files to include"),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "find_instances_of",
|
||||||
|
schema: findInstancesOfSchema,
|
||||||
|
description: `Find all instances of a string in the repository. Returns results with 3 lines of context above and below each match, absolute file paths, and total result count. The working directory this command will be executed in is \`${repoRoot}\`.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createSetTaskStatusToolFields() {
|
export function createSetTaskStatusToolFields() {
|
||||||
const setTaskStatusToolSchema = z.object({
|
const setTaskStatusToolSchema = z.object({
|
||||||
reasoning: z
|
reasoning: z
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue