summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/tool/plan.ts167
-rw-r--r--packages/opencode/src/tool/registry.ts9
2 files changed, 66 insertions, 110 deletions
diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts
index e91bc3faa..aa9c69884 100644
--- a/packages/opencode/src/tool/plan.ts
+++ b/packages/opencode/src/tool/plan.ts
@@ -1,5 +1,6 @@
import z from "zod"
import path from "path"
+import { Effect } from "effect"
import { Tool } from "./tool"
import { Question } from "../question"
import { Session } from "../session"
@@ -9,123 +10,71 @@ import { Instance } from "../project/instance"
import { type SessionID, MessageID, PartID } from "../session/schema"
import EXIT_DESCRIPTION from "./plan-exit.txt"
-async function getLastModel(sessionID: SessionID) {
- for await (const item of MessageV2.stream(sessionID)) {
+function getLastModel(sessionID: SessionID) {
+ for (const item of MessageV2.stream(sessionID)) {
if (item.info.role === "user" && item.info.model) return item.info.model
}
- return Provider.defaultModel()
+ return undefined
}
-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: MessageID.ascending(),
- sessionID: ctx.sessionID,
- role: "user",
- time: {
- created: Date.now(),
- },
- agent: "build",
- model,
- }
- await Session.updateMessage(userMsg)
- await Session.updatePart({
- id: PartID.ascending(),
- 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)
+export const PlanExitTool = Tool.defineEffect(
+ "plan_exit",
+ Effect.gen(function* () {
+ const session = yield* Session.Service
+ const question = yield* Question.Service
+ const provider = yield* Provider.Service
return {
- title: "Switching to build agent",
- output: "User approved switching to build agent. Wait for further instructions.",
- metadata: {},
- }
- },
-})
+ description: EXIT_DESCRIPTION,
+ parameters: z.object({}),
+ execute: (_params: {}, ctx: Tool.Context) =>
+ Effect.gen(function* () {
+ const info = yield* session.get(ctx.sessionID)
+ const plan = path.relative(Instance.worktree, Session.plan(info))
+ const answers = yield* 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,
+ })
-/*
-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))
+ if (answers[0]?.[0] === "No") yield* new Question.RejectedError()
- 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 model = getLastModel(ctx.sessionID) ?? (yield* provider.defaultModel())
- const answer = answers[0]?.[0]
+ const msg: MessageV2.User = {
+ id: MessageID.ascending(),
+ sessionID: ctx.sessionID,
+ role: "user",
+ time: { created: Date.now() },
+ agent: "build",
+ model,
+ }
+ yield* session.updateMessage(msg)
+ yield* session.updatePart({
+ id: PartID.ascending(),
+ messageID: msg.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)
- if (answer === "No") throw new Question.RejectedError()
-
- const model = await getLastModel(ctx.sessionID)
-
- const userMsg: MessageV2.User = {
- id: MessageID.ascending(),
- sessionID: ctx.sessionID,
- role: "user",
- time: {
- created: Date.now(),
- },
- agent: "plan",
- model,
- }
- await Session.updateMessage(userMsg)
- await Session.updatePart({
- id: PartID.ascending(),
- 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: {},
+ return {
+ title: "Switching to build agent",
+ output: "User approved switching to build agent. Wait for further instructions.",
+ metadata: {},
+ }
+ }).pipe(Effect.runPromise),
}
- },
-})
-*/
+ }),
+)
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index b653c336e..716a8ca4e 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -1,4 +1,5 @@
import { PlanExitTool } from "./plan"
+import { Session } from "../session"
import { QuestionTool } from "./question"
import { BashTool } from "./bash"
import { EditTool } from "./edit"
@@ -16,6 +17,7 @@ import { Config } from "../config/config"
import { type ToolContext as PluginToolContext, type ToolDefinition } from "@opencode-ai/plugin"
import z from "zod"
import { Plugin } from "../plugin"
+import { Provider } from "../provider/provider"
import { ProviderID, type ModelID } from "../provider/schema"
import { WebSearchTool } from "./websearch"
import { CodeSearchTool } from "./codesearch"
@@ -76,6 +78,8 @@ export namespace ToolRegistry {
| Todo.Service
| Agent.Service
| Skill.Service
+ | Session.Service
+ | Provider.Service
| LSP.Service
| FileTime.Service
| Instruction.Service
@@ -93,6 +97,7 @@ export namespace ToolRegistry {
const question = yield* QuestionTool
const todo = yield* TodoWriteTool
const lsptool = yield* LspTool
+ const plan = yield* PlanExitTool
const state = yield* InstanceState.make<State>(
Effect.fn("ToolRegistry.state")(function* (ctx) {
@@ -166,7 +171,7 @@ export namespace ToolRegistry {
patch: Tool.init(ApplyPatchTool),
question: Tool.init(question),
lsp: Tool.init(lsptool),
- plan: Tool.init(PlanExitTool),
+ plan: Tool.init(plan),
})
return {
@@ -298,6 +303,8 @@ export namespace ToolRegistry {
Layer.provide(Todo.defaultLayer),
Layer.provide(Skill.defaultLayer),
Layer.provide(Agent.defaultLayer),
+ Layer.provide(Session.defaultLayer),
+ Layer.provide(Provider.defaultLayer),
Layer.provide(LSP.defaultLayer),
Layer.provide(FileTime.defaultLayer),
Layer.provide(Instruction.defaultLayer),