summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/acp/agent.ts40
-rw-r--r--packages/opencode/src/command/index.ts27
-rw-r--r--packages/opencode/src/command/template/initialize.txt (renamed from packages/opencode/src/session/prompt/initialize.txt)2
-rw-r--r--packages/opencode/src/project/bootstrap.ts10
-rw-r--r--packages/opencode/src/session/index.ts32
-rw-r--r--packages/opencode/src/session/prompt.ts30
6 files changed, 92 insertions, 49 deletions
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index ae9a74a66..1eae36e66 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -28,8 +28,6 @@ import { Storage } from "@/storage/storage"
import { Command } from "@/command"
import { Agent as Agents } from "@/agent/agent"
import { Permission } from "@/permission"
-import { Session } from "@/session"
-import { Identifier } from "@/id/id"
import { SessionCompaction } from "@/session/compaction"
import type { Config } from "@/config/config"
import { MCP } from "@/mcp"
@@ -89,7 +87,11 @@ export namespace ACP {
})
if (!res) return
if (res.outcome.outcome !== "selected") {
- Permission.respond({ sessionID: permission.sessionID, permissionID: permission.id, response: "reject" })
+ Permission.respond({
+ sessionID: permission.sessionID,
+ permissionID: permission.id,
+ response: "reject",
+ })
return
}
Permission.respond({
@@ -111,9 +113,11 @@ export namespace ACP {
const acpSession = this.sessionManager.get(part.sessionID)
if (!acpSession) return
- const message = await Storage.read<MessageV2.Info>(["message", part.sessionID, part.messageID]).catch(
- () => undefined,
- )
+ const message = await Storage.read<MessageV2.Info>([
+ "message",
+ part.sessionID,
+ part.messageID,
+ ]).catch(() => undefined)
if (!message || message.role !== "assistant") return
if (part.type === "tool") {
@@ -192,7 +196,9 @@ export namespace ACP {
sessionUpdate: "plan",
entries: parsedTodos.data.map((todo) => {
const status: PlanEntry["status"] =
- todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
+ todo.status === "cancelled"
+ ? "completed"
+ : (todo.status as PlanEntry["status"])
return {
priority: "medium",
status,
@@ -375,11 +381,6 @@ export namespace ACP {
description: command.description ?? "",
}))
const names = new Set(availableCommands.map((c) => c.name))
- if (!names.has("init"))
- availableCommands.push({
- name: "init",
- description: "create/update a AGENTS.md",
- })
if (!names.has("compact"))
availableCommands.push({
name: "compact",
@@ -404,7 +405,8 @@ export namespace ACP {
description: agent.description,
}))
- const currentModeId = availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
+ const currentModeId =
+ availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
const mcpServers: Record<string, Config.Mcp> = {}
for (const server of params.mcpServers) {
@@ -585,14 +587,6 @@ export namespace ACP {
}
switch (cmd.name) {
- case "init":
- await Session.initialize({
- sessionID,
- messageID: Identifier.ascending("message"),
- providerID: model.providerID,
- modelID: model.modelID,
- })
- break
case "compact":
await SessionCompaction.run({
sessionID,
@@ -665,7 +659,9 @@ export namespace ACP {
function parseUri(
uri: string,
- ): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
+ ):
+ | { type: "file"; url: string; filename: string; mime: string }
+ | { type: "text"; text: string } {
try {
if (uri.startsWith("file://")) {
const path = uri.slice(7)
diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts
index c6b24c75b..5e1ad9dc4 100644
--- a/packages/opencode/src/command/index.ts
+++ b/packages/opencode/src/command/index.ts
@@ -1,8 +1,27 @@
import z from "zod"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
+import PROMPT_INITIALIZE from "./template/initialize.txt"
+import { Bus } from "../bus"
+import { Identifier } from "../id/id"
export namespace Command {
+ export const Default = {
+ INIT: "init",
+ } as const
+
+ export const Event = {
+ Executed: Bus.event(
+ "command.executed",
+ z.object({
+ name: z.string(),
+ sessionID: Identifier.schema("session"),
+ arguments: z.string(),
+ messageID: Identifier.schema("message"),
+ }),
+ ),
+ }
+
export const Info = z
.object({
name: z.string(),
@@ -33,6 +52,14 @@ export namespace Command {
}
}
+ if (result[Default.INIT] === undefined) {
+ result[Default.INIT] = {
+ name: Default.INIT,
+ description: "create/update AGENTS.md",
+ template: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
+ }
+ }
+
return result
})
diff --git a/packages/opencode/src/session/prompt/initialize.txt b/packages/opencode/src/command/template/initialize.txt
index 4e45b4c78..5bb59c36c 100644
--- a/packages/opencode/src/session/prompt/initialize.txt
+++ b/packages/opencode/src/command/template/initialize.txt
@@ -6,3 +6,5 @@ The file you create will be given to agentic coding agents (such as yourself) th
If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
If there's already an AGENTS.md, improve it if it's located in ${path}
+
+$ARGUMENTS
diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts
index 45e85fd24..27451d225 100644
--- a/packages/opencode/src/project/bootstrap.ts
+++ b/packages/opencode/src/project/bootstrap.ts
@@ -5,6 +5,10 @@ import { LSP } from "../lsp"
import { FileWatcher } from "../file/watcher"
import { File } from "../file"
import { Flag } from "../flag/flag"
+import { Project } from "./project"
+import { Bus } from "../bus"
+import { Command } from "../command"
+import { Instance } from "./instance"
export async function InstanceBootstrap() {
if (Flag.OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP) return
@@ -14,4 +18,10 @@ export async function InstanceBootstrap() {
await LSP.init()
FileWatcher.init()
File.init()
+
+ Bus.subscribe(Command.Event.Executed, async (payload) => {
+ if (payload.properties.name === Command.Default.INIT) {
+ await Project.setInitialized(Instance.project.id)
+ }
+ })
}
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 2cd1bcd29..71d59d84e 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -2,8 +2,6 @@ import { Decimal } from "decimal.js"
import z from "zod"
import { type LanguageModelUsage, type ProviderMetadata } from "ai"
-import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
-
import { Bus } from "../bus"
import { Config } from "../config/config"
import { Flag } from "../flag/flag"
@@ -14,11 +12,11 @@ import { Share } from "../share/share"
import { Storage } from "../storage/storage"
import { Log } from "../util/log"
import { MessageV2 } from "./message-v2"
-import { Project } from "../project/project"
import { Instance } from "../project/instance"
import { SessionPrompt } from "./prompt"
import { fn } from "@/util/fn"
import { Snapshot } from "@/snapshot"
+import { Command } from "../command"
export namespace Session {
const log = Log.create({ service: "session" })
@@ -164,7 +162,12 @@ export namespace Session {
})
})
- export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) {
+ export async function createNext(input: {
+ id?: string
+ title?: string
+ parentID?: string
+ directory: string
+ }) {
const result: Info = {
id: Identifier.descending("session", input.id),
version: Installation.VERSION,
@@ -402,7 +405,9 @@ export namespace Session {
.add(new Decimal(tokens.input).mul(input.model.cost?.input ?? 0).div(1_000_000))
.add(new Decimal(tokens.output).mul(input.model.cost?.output ?? 0).div(1_000_000))
.add(new Decimal(tokens.cache.read).mul(input.model.cost?.cache_read ?? 0).div(1_000_000))
- .add(new Decimal(tokens.cache.write).mul(input.model.cost?.cache_write ?? 0).div(1_000_000))
+ .add(
+ new Decimal(tokens.cache.write).mul(input.model.cost?.cache_write ?? 0).div(1_000_000),
+ )
.toNumber(),
tokens,
}
@@ -423,22 +428,13 @@ export namespace Session {
messageID: Identifier.schema("message"),
}),
async (input) => {
- await SessionPrompt.prompt({
+ await SessionPrompt.command({
sessionID: input.sessionID,
messageID: input.messageID,
- model: {
- providerID: input.providerID,
- modelID: input.modelID,
- },
- parts: [
- {
- id: Identifier.ascending("part"),
- type: "text",
- text: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
- },
- ],
+ model: input.providerID + "/" + input.modelID,
+ command: Command.Default.INIT,
+ arguments: "",
})
- await Project.setInitialized(Instance.project.id)
},
)
}
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 2f1505869..5cbfb8b5a 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -1593,6 +1593,7 @@ export namespace SessionPrompt {
let index = 0
template = template.replace(bashRegex, () => results[index++])
}
+ template = template.trim()
const parts = [
{
@@ -1657,6 +1658,8 @@ export namespace SessionPrompt {
})()
const agent = await Agent.get(agentName)
+ let result: MessageV2.WithParts
+
if ((agent.mode === "subagent" && command.subtask !== false) || command.subtask === true) {
using abort = lock(input.sessionID)
@@ -1732,7 +1735,7 @@ export namespace SessionPrompt {
}
await Session.updatePart(toolPart)
- const result = await TaskTool.init().then((t) =>
+ const taskResult = await TaskTool.init().then((t) =>
t.execute(args, {
sessionID: input.sessionID,
abort: abort.signal,
@@ -1760,22 +1763,31 @@ export namespace SessionPrompt {
},
input: toolPart.state.input,
title: "",
- metadata: result.metadata,
- output: result.output,
+ metadata: taskResult.metadata,
+ output: taskResult.output,
}
await Session.updatePart(toolPart)
}
- return { info: assistantMsg, parts: [toolPart] }
+ result = { info: assistantMsg, parts: [toolPart] }
+ } else {
+ result = await prompt({
+ sessionID: input.sessionID,
+ messageID: input.messageID,
+ model,
+ agent: agentName,
+ parts,
+ })
}
- return prompt({
+ Bus.publish(Command.Event.Executed, {
+ name: input.command,
sessionID: input.sessionID,
- messageID: input.messageID,
- model,
- agent: agentName,
- parts,
+ arguments: input.arguments,
+ messageID: result.info.id,
})
+
+ return result
}
async function ensureTitle(input: {