plan.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import z from "zod"
  2. import path from "path"
  3. import { Effect } from "effect"
  4. import * as Tool from "./tool"
  5. import { Question } from "../question"
  6. import { Session } from "../session"
  7. import { MessageV2 } from "../session/message-v2"
  8. import { Provider } from "../provider"
  9. import { Instance } from "../project/instance"
  10. import { type SessionID, MessageID, PartID } from "../session/schema"
  11. import EXIT_DESCRIPTION from "./plan-exit.txt"
  12. function getLastModel(sessionID: SessionID) {
  13. for (const item of MessageV2.stream(sessionID)) {
  14. if (item.info.role === "user" && item.info.model) return item.info.model
  15. }
  16. return undefined
  17. }
  18. export const PlanExitTool = Tool.define(
  19. "plan_exit",
  20. Effect.gen(function* () {
  21. const session = yield* Session.Service
  22. const question = yield* Question.Service
  23. const provider = yield* Provider.Service
  24. return {
  25. description: EXIT_DESCRIPTION,
  26. parameters: z.object({}),
  27. execute: (_params: {}, ctx: Tool.Context) =>
  28. Effect.gen(function* () {
  29. const info = yield* session.get(ctx.sessionID)
  30. const plan = path.relative(Instance.worktree, Session.plan(info))
  31. const answers = yield* question.ask({
  32. sessionID: ctx.sessionID,
  33. questions: [
  34. {
  35. question: `Plan at ${plan} is complete. Would you like to switch to the build agent and start implementing?`,
  36. header: "Build Agent",
  37. custom: false,
  38. options: [
  39. { label: "Yes", description: "Switch to build agent and start implementing the plan" },
  40. { label: "No", description: "Stay with plan agent to continue refining the plan" },
  41. ],
  42. },
  43. ],
  44. tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,
  45. })
  46. if (answers[0]?.[0] === "No") yield* new Question.RejectedError()
  47. const model = getLastModel(ctx.sessionID) ?? (yield* provider.defaultModel())
  48. const msg: MessageV2.User = {
  49. id: MessageID.ascending(),
  50. sessionID: ctx.sessionID,
  51. role: "user",
  52. time: { created: Date.now() },
  53. agent: "build",
  54. model,
  55. }
  56. yield* session.updateMessage(msg)
  57. yield* session.updatePart({
  58. id: PartID.ascending(),
  59. messageID: msg.id,
  60. sessionID: ctx.sessionID,
  61. type: "text",
  62. text: `The plan at ${plan} has been approved, you can now edit files. Execute the plan`,
  63. synthetic: true,
  64. } satisfies MessageV2.TextPart)
  65. return {
  66. title: "Switching to build agent",
  67. output: "User approved switching to build agent. Wait for further instructions.",
  68. metadata: {},
  69. }
  70. }).pipe(Effect.orDie),
  71. }
  72. }),
  73. )