2025-03-06 20:04:55 -08:00
|
|
|
import { v4 as uuidv4 } from "uuid";
|
2025-03-07 10:47:08 -08:00
|
|
|
import { AIMessage, ToolMessage } from "@langchain/langgraph-sdk";
|
2025-03-06 20:04:55 -08:00
|
|
|
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
2025-03-07 10:47:08 -08:00
|
|
|
import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses";
|
|
|
|
|
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
|
|
|
|
import ComponentMap from "../../uis";
|
|
|
|
|
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
|
2025-03-06 20:04:55 -08:00
|
|
|
|
2025-03-07 12:43:22 -08:00
|
|
|
const PLAN = [
|
2025-03-07 10:47:08 -08:00
|
|
|
"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.",
|
|
|
|
|
"Add form functionality for creating new todos with input validation and error handling.",
|
|
|
|
|
"Create filtering and sorting capabilities to allow users to view completed, active, or all todos.",
|
|
|
|
|
"Implement local storage integration to persist todo items between page refreshes.",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export async function planner(
|
2025-03-07 11:21:49 -08:00
|
|
|
state: OpenCodeState,
|
2025-03-07 10:47:08 -08:00
|
|
|
config: LangGraphRunnableConfig,
|
|
|
|
|
): Promise<OpenCodeUpdate> {
|
|
|
|
|
const ui = typedUi<typeof ComponentMap>(config);
|
|
|
|
|
|
2025-03-07 12:43:22 -08:00
|
|
|
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;
|
2025-03-07 13:42:37 -08:00
|
|
|
const lastUpdateToolCallResponse = state.messages.findLast(
|
|
|
|
|
(m) =>
|
|
|
|
|
m.getType() === "tool" &&
|
|
|
|
|
(m as unknown as ToolMessage).tool_call_id ===
|
|
|
|
|
lastUpdateCodeToolCall?.tool_calls?.[0]?.id,
|
|
|
|
|
) as ToolMessage | undefined;
|
2025-03-07 11:21:49 -08:00
|
|
|
const lastPlanToolCall = state.messages.findLast(
|
|
|
|
|
(m) =>
|
|
|
|
|
m.getType() === "ai" &&
|
|
|
|
|
(m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"),
|
|
|
|
|
) as AIMessage | undefined;
|
|
|
|
|
|
2025-03-07 13:42:37 -08:00
|
|
|
const wasPlanRejected = (
|
|
|
|
|
lastUpdateToolCallResponse?.content as string | undefined
|
|
|
|
|
)
|
|
|
|
|
?.toLowerCase()
|
|
|
|
|
.includes("rejected");
|
|
|
|
|
|
2025-03-07 14:01:07 -08:00
|
|
|
const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args;
|
2025-03-07 12:43:22 -08:00
|
|
|
const executedPlans: string[] = planToolCallArgs?.executedPlans ?? [];
|
2025-03-07 13:42:37 -08:00
|
|
|
const rejectedPlans: string[] = planToolCallArgs?.rejectedPlans ?? [];
|
2025-03-07 12:43:22 -08:00
|
|
|
let remainingPlans: string[] = planToolCallArgs?.remainingPlans ?? PLAN;
|
|
|
|
|
|
2025-03-07 14:01:07 -08:00
|
|
|
const proposedChangePlanItem: string | undefined =
|
|
|
|
|
lastUpdateCodeToolCall?.tool_calls?.[0]?.args?.executed_plan_item;
|
2025-03-07 13:42:37 -08:00
|
|
|
if (proposedChangePlanItem) {
|
|
|
|
|
if (wasPlanRejected) {
|
|
|
|
|
rejectedPlans.push(proposedChangePlanItem);
|
|
|
|
|
} else {
|
|
|
|
|
executedPlans.push(proposedChangePlanItem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remainingPlans = remainingPlans.filter((p) => p !== proposedChangePlanItem);
|
2025-03-07 12:43:22 -08:00
|
|
|
}
|
|
|
|
|
|
2025-03-07 13:42:37 -08:00
|
|
|
const content = proposedChangePlanItem
|
|
|
|
|
? `I've updated the plan list based on the last proposed change.`
|
2025-03-07 12:43:22 -08:00
|
|
|
: `I've come up with a detailed plan for building the todo app.`;
|
2025-03-07 11:21:49 -08:00
|
|
|
|
2025-03-07 10:47:08 -08:00
|
|
|
const toolCallId = uuidv4();
|
2025-03-06 20:04:55 -08:00
|
|
|
const aiMessage: AIMessage = {
|
|
|
|
|
type: "ai",
|
|
|
|
|
id: uuidv4(),
|
2025-03-07 11:21:49 -08:00
|
|
|
content,
|
2025-03-06 20:04:55 -08:00
|
|
|
tool_calls: [
|
|
|
|
|
{
|
2025-03-07 10:47:08 -08:00
|
|
|
name: "plan",
|
2025-03-06 20:04:55 -08:00
|
|
|
args: {
|
2025-03-07 12:43:22 -08:00
|
|
|
executedPlans,
|
2025-03-07 13:42:37 -08:00
|
|
|
rejectedPlans,
|
2025-03-07 12:43:22 -08:00
|
|
|
remainingPlans,
|
2025-03-06 20:04:55 -08:00
|
|
|
},
|
2025-03-07 10:47:08 -08:00
|
|
|
id: toolCallId,
|
2025-03-06 20:04:55 -08:00
|
|
|
type: "tool_call",
|
2025-03-07 10:47:08 -08:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-07 21:26:48 +01:00
|
|
|
const msg = ui.create("code-plan", {
|
2025-03-07 10:47:08 -08:00
|
|
|
toolCallId,
|
2025-03-07 11:21:49 -08:00
|
|
|
executedPlans,
|
2025-03-07 13:42:37 -08:00
|
|
|
rejectedPlans,
|
2025-03-07 11:21:49 -08:00
|
|
|
remainingPlans,
|
2025-03-07 10:47:08 -08:00
|
|
|
});
|
2025-03-07 21:26:48 +01:00
|
|
|
msg.additional_kwargs["message_id"] = aiMessage.id;
|
2025-03-07 10:47:08 -08:00
|
|
|
|
|
|
|
|
const toolMessage: ToolMessage = {
|
|
|
|
|
type: "tool",
|
|
|
|
|
id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`,
|
|
|
|
|
tool_call_id: toolCallId,
|
|
|
|
|
content: "User has approved the plan.",
|
|
|
|
|
};
|
2025-03-06 20:04:55 -08:00
|
|
|
|
2025-03-07 10:47:08 -08:00
|
|
|
return {
|
|
|
|
|
messages: [aiMessage, toolMessage],
|
2025-03-07 21:26:48 +01:00
|
|
|
ui: [msg],
|
2025-03-07 10:47:08 -08:00
|
|
|
timestamp: Date.now(),
|
|
|
|
|
};
|
|
|
|
|
}
|