plan.ts 2.9 KB

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