diff --git a/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts b/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts index 0b5188f3..7b73a649 100644 --- a/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts +++ b/apps/open-swe/src/graphs/programmer/nodes/progress-plan-step.ts @@ -23,6 +23,7 @@ import { } from "../../../utils/current-task.js"; import { ToolMessage } from "@langchain/core/messages"; import { addTaskPlanToIssue } from "../../../utils/github/issue-task.js"; +import { createSetTaskStatusToolFields } from "@open-swe/shared/open-swe/tools"; const logger = createLogger(LogLevel.INFO, "ProgressPlanStep"); @@ -40,26 +41,6 @@ Take all of this information, and determine whether or not you have completed th Once you've determined the status of the current task, call the \`set_task_status\` tool. `; -const setTaskStatusToolSchema = z.object({ - reasoning: z - .string() - .describe( - "A concise reasoning summary for the status of the current task, explaining why you think it is completed or not completed.", - ), - task_status: z - .enum(["completed", "not_completed"]) - .describe( - "The status of the current task, based on the reasoning provided.", - ), -}); - -const setTaskStatusTool = { - name: "set_task_status", - description: - "The status of the current task, along with a concise reasoning summary to support the status.", - schema: setTaskStatusToolSchema, -}; - const formatPrompt = (taskPlan: PlanItem[]): string => { return systemPrompt.replace( "{PLAN_PROMPT}", @@ -71,6 +52,7 @@ export async function progressPlanStep( state: GraphState, config: GraphConfig, ): Promise { + const setTaskStatusTool = createSetTaskStatusToolFields(); const model = await loadModel(config, Task.PROGRESS_PLAN_CHECKER); const modelWithTools = model.bindTools([setTaskStatusTool], { tool_choice: setTaskStatusTool.name, @@ -109,7 +91,7 @@ Once you've determined the status of the current task, call the \`set_task_statu } const isCompleted = - (toolCall.args as z.infer).task_status === + (toolCall.args as z.infer).task_status === "completed"; const currentTask = getCurrentPlanItem(activePlanItems); const toolMessage = new ToolMessage({ diff --git a/apps/web/src/components/gen-ui/task-summary.tsx b/apps/web/src/components/gen-ui/task-summary.tsx index 5fc2a57e..087513cb 100644 --- a/apps/web/src/components/gen-ui/task-summary.tsx +++ b/apps/web/src/components/gen-ui/task-summary.tsx @@ -1,43 +1,44 @@ "use client"; -import "../app/globals.css"; import { useState } from "react"; import { CheckCircle, Loader2, ChevronDown, ChevronUp, - MessageSquare, FileText, + MinusCircle, } from "lucide-react"; type TaskSummaryProps = { status: "loading" | "generating" | "done"; + completed?: boolean; summary?: string; - reasoningText?: string; summaryText?: string; }; export function TaskSummary({ status, + completed, summary, - reasoningText, summaryText, }: TaskSummaryProps) { const [expanded, setExpanded] = useState(false); - const [showReasoning, setShowReasoning] = useState(false); const [showSummary, setShowSummary] = useState(false); const getStatusIcon = () => { switch (status) { case "loading": - return ( -
- ); + return
; case "generating": - return ; + return ( + + ); case "done": - return ; + if (completed === false) { + return ; + } + return ; } }; @@ -48,31 +49,17 @@ export function TaskSummary({ case "generating": return "Generating summary..."; case "done": + if (completed === false) { + return "Task not completed"; + } return "Task completed"; } }; return ( -
- {reasoningText && ( -
- - {showReasoning && ( -

- {reasoningText} -

- )} -
- )} - +
setExpanded(!expanded) @@ -80,40 +67,56 @@ export function TaskSummary({ } > {getStatusIcon()} - + {getStatusText()} {status === "done" && summary && ( - )}
{expanded && summary && status === "done" && ( -
-

+
+

Task Summary

-

{summary}

+

{summary}

)} {summaryText && status === "done" && ( -
+
{showSummary && ( -

+

{summaryText}

)} diff --git a/apps/web/src/components/thread/messages/ai.tsx b/apps/web/src/components/thread/messages/ai.tsx index 710bd0f3..49d20d2f 100644 --- a/apps/web/src/components/thread/messages/ai.tsx +++ b/apps/web/src/components/thread/messages/ai.tsx @@ -19,10 +19,12 @@ import { Fragment } from "react/jsx-runtime"; import { useQueryState, parseAsBoolean } from "nuqs"; import { Interrupt } from "./interrupt"; import { ActionStep, ActionItemProps } from "@/components/gen-ui/action-step"; +import { TaskSummary } from "@/components/gen-ui/task-summary"; import { ToolCall } from "@langchain/core/messages/tool"; import { createApplyPatchToolFields, createShellToolFields, + createSetTaskStatusToolFields, } from "@open-swe/shared/open-swe/tools"; import { z } from "zod"; import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages"; @@ -34,6 +36,8 @@ const shellTool = createShellToolFields(dummyRepo); type ShellToolArgs = z.infer; const applyPatchTool = createApplyPatchToolFields(dummyRepo); type ApplyPatchToolArgs = z.infer; +const setTaskStatusTool = createSetTaskStatusToolFields(); +type SetTaskStatusToolArgs = z.infer; function CustomComponent({ message, @@ -193,6 +197,32 @@ export function AssistantMessage({ ) : []; + const taskStatusToolCall = message + ? aiToolCalls.find((tc) => tc.name === setTaskStatusTool.name) + : undefined; + + // We can be sure that if the task status tool call is present, it will be the + // only tool call/result we need to render for this message. + if (taskStatusToolCall) { + const args = taskStatusToolCall.args as SetTaskStatusToolArgs; + const correspondingToolResult = toolResults.find( + (tr) => tr && tr.tool_call_id === taskStatusToolCall.id, + ); + + const status = correspondingToolResult ? "done" : "generating"; + const completed = args.task_status === "completed"; + + return ( +
+ +
+ ); + } + if (shellOrPatchToolCalls.length > 0) { const actionItems = shellOrPatchToolCalls.map((toolCall) => { const correspondingToolResult = toolResults.find( diff --git a/packages/shared/src/open-swe/tools.ts b/packages/shared/src/open-swe/tools.ts index 90c844cd..9cf04ddd 100644 --- a/packages/shared/src/open-swe/tools.ts +++ b/packages/shared/src/open-swe/tools.ts @@ -100,3 +100,27 @@ export function createUpdatePlanToolFields() { "\nThe reasoning you pass to this tool will be used in the step that actually updates the plan, so ensure it is useful and concise.", }; } + +export function createSetTaskStatusToolFields() { + const setTaskStatusToolSchema = z.object({ + reasoning: z + .string() + .describe( + "A concise reasoning summary for the status of the current task, explaining why you think it is completed or not completed.", + ), + task_status: z + .enum(["completed", "not_completed"]) + .describe( + "The status of the current task, based on the reasoning provided.", + ), + }); + + const setTaskStatusTool = { + name: "set_task_status", + description: + "The status of the current task, along with a concise reasoning summary to support the status.", + schema: setTaskStatusToolSchema, + }; + + return setTaskStatusTool; +}