130 lines · 4.2 KB · TypeScript
RawDownload
import z from "zod"import path from "path"import { Tool } from "./tool"import { Question } from "../question"import { Session } from "../session"import { MessageV2 } from "../session/message-v2"import { Identifier } from "../id/id"import { Provider } from "../provider/provider"import { Instance } from "../project/instance"import EXIT_DESCRIPTION from "./plan-exit.txt"import ENTER_DESCRIPTION from "./plan-enter.txt" async function getLastModel(sessionID: string) {  for await (const item of MessageV2.stream(sessionID)) {    if (item.info.role === "user" && item.info.model) return item.info.model  }  return Provider.defaultModel()} export const PlanExitTool = Tool.define("plan_exit", {  description: EXIT_DESCRIPTION,  parameters: z.object({}),  async execute(_params, ctx) {    const session = await Session.get(ctx.sessionID)    const plan = path.relative(Instance.worktree, Session.plan(session))    const answers = await Question.ask({      sessionID: ctx.sessionID,      questions: [        {          question: `Plan at ${plan} is complete. Would you like to switch to the build agent and start implementing?`,          header: "Build Agent",          custom: false,          options: [            { label: "Yes", description: "Switch to build agent and start implementing the plan" },            { label: "No", description: "Stay with plan agent to continue refining the plan" },          ],        },      ],      tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,    })     const answer = answers[0]?.[0]    if (answer === "No") throw new Question.RejectedError()     const model = await getLastModel(ctx.sessionID)     const userMsg: MessageV2.User = {      id: Identifier.ascending("message"),      sessionID: ctx.sessionID,      role: "user",      time: {        created: Date.now(),      },      agent: "research",      model,    }    await Session.updateMessage(userMsg)    await Session.updatePart({      id: Identifier.ascending("part"),      messageID: userMsg.id,      sessionID: ctx.sessionID,      type: "text",      text: `The plan at ${plan} has been approved, you can now edit files. Execute the plan`,      synthetic: true,    } satisfies MessageV2.TextPart)     return {      title: "Switching to build agent",      output: "User approved switching to build agent. Wait for further instructions.",      metadata: {},    }  },}) export const PlanEnterTool = Tool.define("plan_enter", {  description: ENTER_DESCRIPTION,  parameters: z.object({}),  async execute(_params, ctx) {    const session = await Session.get(ctx.sessionID)    const plan = path.relative(Instance.worktree, Session.plan(session))     const answers = await Question.ask({      sessionID: ctx.sessionID,      questions: [        {          question: `Would you like to switch to the plan agent and create a plan saved to ${plan}?`,          header: "Plan Mode",          custom: false,          options: [            { label: "Yes", description: "Switch to plan agent for research and planning" },            { label: "No", description: "Stay with build agent to continue making changes" },          ],        },      ],      tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,    })     const answer = answers[0]?.[0]     if (answer === "No") throw new Question.RejectedError()     const model = await getLastModel(ctx.sessionID)     const userMsg: MessageV2.User = {      id: Identifier.ascending("message"),      sessionID: ctx.sessionID,      role: "user",      time: {        created: Date.now(),      },      agent: "plan",      model,    }    await Session.updateMessage(userMsg)    await Session.updatePart({      id: Identifier.ascending("part"),      messageID: userMsg.id,      sessionID: ctx.sessionID,      type: "text",      text: "User has requested to enter plan mode. Switch to plan mode and begin planning.",      synthetic: true,    } satisfies MessageV2.TextPart)     return {      title: "Switching to plan agent",      output: `User confirmed to switch to plan mode. A new message has been created to switch you to plan mode. The plan file will be at ${plan}. Begin planning.`,      metadata: {},    }  },})