diff --git a/agent/open-code/nodes/executor.ts b/agent/open-code/nodes/executor.ts index 6276e1e..738c609 100644 --- a/agent/open-code/nodes/executor.ts +++ b/agent/open-code/nodes/executor.ts @@ -5,7 +5,6 @@ import { OpenCodeState, OpenCodeUpdate } from "../types"; import { LangGraphRunnableConfig } from "@langchain/langgraph"; import ComponentMap from "../../uis"; import { typedUi } from "@langchain/langgraph-sdk/react-ui/server"; -import { PLAN } from "./planner"; export async function executor( state: OpenCodeState, @@ -18,11 +17,25 @@ export async function executor( m.getType() === "ai" && (m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"), ) as AIMessage | undefined; - const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args?.args; - const numOfExecutedPlanItems: number = - planToolCallArgs?.executedPlans?.length ?? 0; + const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args as Record< + string, + any + >; + const nextPlanItem = planToolCallArgs?.remainingPlans?.[0] as + | string + | undefined; + const numOfExecutedPlanItems = planToolCallArgs?.executedPlans?.length ?? 0; - const planItem = PLAN[numOfExecutedPlanItems - 1]; + if (!nextPlanItem) { + // All plans have been executed + const successfullyFinishedMsg: AIMessage = { + type: "ai", + id: uuidv4(), + content: + "Successfully completed all the steps in the plan. Please let me know if you need anything else!", + }; + return { messages: [successfullyFinishedMsg] }; + } let updateFileContents = ""; switch (numOfExecutedPlanItems) { @@ -79,9 +92,8 @@ export async function executor( { name: "update_file", args: { - args: { - new_file_content: updateFileContents, - }, + new_file_content: updateFileContents as any, + executed_plan_item: nextPlanItem as any, }, id: toolCallId, type: "tool_call", @@ -92,7 +104,7 @@ export async function executor( ui.write("proposed-change", { toolCallId, change: updateFileContents, - planItem, + planItem: nextPlanItem, }); return { diff --git a/agent/open-code/nodes/planner.ts b/agent/open-code/nodes/planner.ts index 2c5f94b..6454483 100644 --- a/agent/open-code/nodes/planner.ts +++ b/agent/open-code/nodes/planner.ts @@ -6,7 +6,7 @@ import { LangGraphRunnableConfig } from "@langchain/langgraph"; import ComponentMap from "../../uis"; import { typedUi } from "@langchain/langgraph-sdk/react-ui/server"; -export const PLAN = [ +const PLAN = [ "Set up project scaffolding using Create React App and implement basic folder structure for components, styles, and utilities.", "Create reusable UI components for TodoItem, including styling with CSS modules.", "Implement state management using React Context to handle todo items, including actions for adding, updating, and deleting todos.", @@ -21,19 +21,36 @@ export async function planner( ): Promise { const ui = typedUi(config); + const lastUpdateCodeToolCall = state.messages.findLast( + (m) => + m.getType() === "ai" && + (m as unknown as AIMessage).tool_calls?.some( + (tc) => tc.name === "update_file", + ), + ) as AIMessage | undefined; const lastPlanToolCall = state.messages.findLast( (m) => m.getType() === "ai" && (m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"), ) as AIMessage | undefined; - const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args?.args; - const executedPlans = planToolCallArgs?.executedPlans ?? []; - const remainingPlans = planToolCallArgs?.remainingPlans ?? PLAN; - const content = - executedPlans.length > 0 - ? `I've updated the plan list based on the executed plans.` - : `I've come up with a detailed plan for building the todo app.`; + const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args as Record< + string, + any + >; + const executedPlans: string[] = planToolCallArgs?.executedPlans ?? []; + let remainingPlans: string[] = planToolCallArgs?.remainingPlans ?? PLAN; + + const executedPlanItem = lastUpdateCodeToolCall?.tool_calls?.[0]?.args + ?.executed_plan_item as string | undefined; + if (executedPlanItem) { + executedPlans.push(executedPlanItem); + remainingPlans = remainingPlans.filter((p) => p !== executedPlanItem); + } + + const content = executedPlanItem + ? `I've updated the plan list based on the executed plans.` + : `I've come up with a detailed plan for building the todo app.`; const toolCallId = uuidv4(); const aiMessage: AIMessage = { @@ -44,10 +61,8 @@ export async function planner( { name: "plan", args: { - args: { - executedPlans, - remainingPlans, - }, + executedPlans, + remainingPlans, }, id: toolCallId, type: "tool_call", diff --git a/agent/uis/open-code/plan/index.tsx b/agent/uis/open-code/plan/index.tsx index 46a6166..0714577 100644 --- a/agent/uis/open-code/plan/index.tsx +++ b/agent/uis/open-code/plan/index.tsx @@ -8,9 +8,9 @@ interface PlanProps { export default function Plan(props: PlanProps) { return ( -
+

Code Plan

-
+

Executed Plans diff --git a/agent/uis/open-code/proposed-change/index.tsx b/agent/uis/open-code/proposed-change/index.tsx index 7a41980..f6fac0e 100644 --- a/agent/uis/open-code/proposed-change/index.tsx +++ b/agent/uis/open-code/proposed-change/index.tsx @@ -7,7 +7,8 @@ import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; import { UIMessage, useStreamContext } from "@langchain/langgraph-sdk/react-ui"; import { Message } from "@langchain/langgraph-sdk"; import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses"; -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { getToolResponse } from "../../utils/get-tool-response"; interface ProposedChangeProps { toolCallId: string; @@ -15,6 +16,9 @@ interface ProposedChangeProps { planItem: string; } +const ACCEPTED_CHANGE_CONTENT = + "User accepted the proposed change. Please continue."; + export default function ProposedChange(props: ProposedChangeProps) { const [isAccepted, setIsAccepted] = useState(false); @@ -27,7 +31,6 @@ export default function ProposedChange(props: ProposedChangeProps) { alert("Rejected. (just kidding, you can't reject me silly!)"); }; const handleAccept = () => { - const content = "User accepted the proposed change. Please continue."; thread.submit({ messages: [ { @@ -35,7 +38,7 @@ export default function ProposedChange(props: ProposedChangeProps) { tool_call_id: props.toolCallId, id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`, name: "buy-stock", - content, + content: ACCEPTED_CHANGE_CONTENT, }, { type: "human", @@ -47,9 +50,17 @@ export default function ProposedChange(props: ProposedChangeProps) { setIsAccepted(true); }; + useEffect(() => { + if (typeof window === "undefined" || isAccepted) return; + const toolResponse = getToolResponse(props.toolCallId, thread); + if (toolResponse && toolResponse.content === ACCEPTED_CHANGE_CONTENT) { + setIsAccepted(true); + } + }, []); + if (isAccepted) { return ( -
+

Accepted Change

{props.planItem}

diff --git a/src/components/thread/messages/human.tsx b/src/components/thread/messages/human.tsx index 7c0b553..a394874 100644 --- a/src/components/thread/messages/human.tsx +++ b/src/components/thread/messages/human.tsx @@ -84,7 +84,9 @@ export function HumanMessage({ onSubmit={handleSubmitEdit} /> ) : ( -

{contentString}

+

+ {contentString} +

)}