summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-06-11 18:19:15 -0400
committerDax Raad <[email protected]>2025-06-11 18:19:21 -0400
commit468cec545a634fc5dcae37b8557ca2fc721cf0f1 (patch)
treedf0e907c17598826a5743690f0205428bfa70a42
parent3c82fb68186e3f86f7b64dbd6e9f63ba51222aad (diff)
downloadopencode-468cec545a634fc5dcae37b8557ca2fc721cf0f1.tar.gz
opencode-468cec545a634fc5dcae37b8557ca2fc721cf0f1.zip
sync
-rw-r--r--packages/opencode/src/cli/cmd/run.ts56
-rw-r--r--packages/opencode/src/session/index.ts23
-rw-r--r--packages/opencode/src/session/message.ts7
-rw-r--r--packages/opencode/src/storage/storage.ts39
-rw-r--r--packages/opencode/src/tool/grep.ts5
-rw-r--r--packages/opencode/src/tool/todo.ts4
-rw-r--r--packages/opencode/src/util/log.ts2
7 files changed, 84 insertions, 52 deletions
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 522f35b89..d7b0223a3 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -8,6 +8,25 @@ import { Message } from "../../session/message"
import { UI } from "../ui"
import { VERSION } from "../version"
+const COLOR = [
+ UI.Style.TEXT_SUCCESS_BOLD,
+ UI.Style.TEXT_INFO_BOLD,
+ UI.Style.TEXT_HIGHLIGHT_BOLD,
+ UI.Style.TEXT_WARNING_BOLD,
+]
+
+const TOOL: Record<string, [string, string]> = {
+ opencode_todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
+ opencode_todoread: ["Todo", UI.Style.TEXT_WARNING_BOLD],
+ opencode_bash: ["Bash", UI.Style.TEXT_DANGER_BOLD],
+ opencode_edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
+ opencode_glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
+ opencode_grep: ["Grep", UI.Style.TEXT_INFO_BOLD],
+ opencode_list: ["List", UI.Style.TEXT_INFO_BOLD],
+ opencode_read: ["Read", UI.Style.TEXT_HIGHLIGHT_BOLD],
+ opencode_write: ["Write", UI.Style.TEXT_SUCCESS_BOLD],
+}
+
export const RunCommand = {
command: "run [message..]",
describe: "Run OpenCode with a message",
@@ -63,33 +82,24 @@ export const RunCommand = {
)
}
- Bus.subscribe(Message.Event.PartUpdated, async (message) => {
- const part = message.properties.part
+ Bus.subscribe(Message.Event.PartUpdated, async (evt) => {
+ const part = evt.properties.part
+ const message = await Session.getMessage(
+ evt.properties.sessionID,
+ evt.properties.messageID,
+ )
+
if (
part.type === "tool-invocation" &&
part.toolInvocation.state === "result"
) {
- if (part.toolInvocation.toolName === "opencode_todowrite") return
-
- const args = part.toolInvocation.args as any
- const tool = part.toolInvocation.toolName
-
- if (tool === "opencode_edit")
- printEvent(UI.Style.TEXT_SUCCESS_BOLD, "Edit", args.filePath)
- if (tool === "opencode_bash")
- printEvent(UI.Style.TEXT_WARNING_BOLD, "Execute", args.command)
- if (tool === "opencode_read")
- printEvent(UI.Style.TEXT_INFO_BOLD, "Read", args.filePath)
- if (tool === "opencode_write")
- printEvent(UI.Style.TEXT_SUCCESS_BOLD, "Create", args.filePath)
- if (tool === "opencode_list")
- printEvent(UI.Style.TEXT_INFO_BOLD, "List", args.path)
- if (tool === "opencode_glob")
- printEvent(
- UI.Style.TEXT_INFO_BOLD,
- "Glob",
- args.pattern + (args.path ? " in " + args.path : ""),
- )
+ const metadata =
+ message.metadata.tool[part.toolInvocation.toolCallId]
+ const [tool, color] = TOOL[part.toolInvocation.toolName] ?? [
+ part.toolInvocation.toolName,
+ UI.Style.TEXT_INFO_BOLD,
+ ]
+ printEvent(color, tool, metadata.title)
}
if (part.type === "text") {
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 97ea5db4b..674acf7cb 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -143,14 +143,19 @@ export namespace Session {
const result = [] as Message.Info[]
const list = Storage.list("session/message/" + sessionID)
for await (const p of list) {
- const read = await Storage.readJSON<Message.Info>(p).catch(() => {})
- if (!read) continue
+ const read = await Storage.readJSON<Message.Info>(p)
result.push(read)
}
result.sort((a, b) => (a.id > b.id ? 1 : -1))
return result
}
+ export async function getMessage(sessionID: string, messageID: string) {
+ return Storage.readJSON<Message.Info>(
+ "session/message/" + sessionID + "/" + messageID,
+ )
+ }
+
export async function* list() {
for await (const item of Storage.list("session/info")) {
const sessionID = path.basename(item, ".json")
@@ -371,16 +376,19 @@ export namespace Session {
end: Date.now(),
},
}
+ await updateMessage(next)
return result.output
} catch (e: any) {
next.metadata!.tool![opts.toolCallId] = {
error: true,
message: e.toString(),
+ title: e.toString(),
time: {
start,
end: Date.now(),
},
}
+ await updateMessage(next)
return e.toString()
}
},
@@ -400,6 +408,7 @@ export namespace Session {
end: Date.now(),
},
}
+ await updateMessage(next)
return result.content
.filter((x: any) => x.type === "text")
.map((x: any) => x.text)
@@ -408,11 +417,13 @@ export namespace Session {
next.metadata!.tool![opts.toolCallId] = {
error: true,
message: e.toString(),
+ title: "mcp",
time: {
start,
end: Date.now(),
},
}
+ await updateMessage(next)
return e.toString()
}
}
@@ -433,6 +444,8 @@ export namespace Session {
if (text) {
Bus.publish(Message.Event.PartUpdated, {
part: text,
+ messageID: next.id,
+ sessionID: next.metadata.sessionID,
})
}
text = undefined
@@ -463,6 +476,8 @@ export namespace Session {
})
Bus.publish(Message.Event.PartUpdated, {
part: next.parts[next.parts.length - 1],
+ messageID: next.id,
+ sessionID: next.metadata.sessionID,
})
break
@@ -478,6 +493,8 @@ export namespace Session {
})
Bus.publish(Message.Event.PartUpdated, {
part: next.parts[next.parts.length - 1],
+ messageID: next.id,
+ sessionID: next.metadata.sessionID,
})
break
@@ -500,6 +517,8 @@ export namespace Session {
}
Bus.publish(Message.Event.PartUpdated, {
part: match,
+ messageID: next.id,
+ sessionID: next.metadata.sessionID,
})
}
break
diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts
index a5b56cc8b..d9ac88d70 100644
--- a/packages/opencode/src/session/message.ts
+++ b/packages/opencode/src/session/message.ts
@@ -151,7 +151,7 @@ export namespace Message {
z.string(),
z
.object({
- title: z.string().optional(),
+ title: z.string(),
time: z.object({
start: z.number(),
end: z.number(),
@@ -186,6 +186,9 @@ export namespace Message {
info: Info,
}),
),
- PartUpdated: Bus.event("message.part.updated", z.object({ part: Part })),
+ PartUpdated: Bus.event(
+ "message.part.updated",
+ z.object({ part: Part, sessionID: z.string(), messageID: z.string() }),
+ ),
}
}
diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts
index 76823e74c..ab185cafc 100644
--- a/packages/opencode/src/storage/storage.ts
+++ b/packages/opencode/src/storage/storage.ts
@@ -1,11 +1,9 @@
-import { FileStorage } from "@flystorage/file-storage"
-import { LocalStorageAdapter } from "@flystorage/local-fs"
-import fs from "fs/promises"
import { Log } from "../util/log"
import { App } from "../app/app"
import { Bus } from "../bus"
import path from "path"
import z from "zod"
+import fs from "fs/promises"
export namespace Storage {
const log = Log.create({ service: "storage" })
@@ -17,36 +15,39 @@ export namespace Storage {
),
}
- const state = App.state("storage", async () => {
+ const state = App.state("storage", () => {
const app = App.info()
- const storageDir = path.join(app.path.data, "storage")
- await fs.mkdir(storageDir, { recursive: true })
- const storage = new FileStorage(new LocalStorageAdapter(storageDir))
- log.info("created", { path: storageDir })
+ const dir = path.join(app.path.data, "storage")
+ log.info("init", { path: dir })
return {
- storage,
+ dir,
}
})
+ const locks = new Map<string, Promise<void>>()
+
export async function readJSON<T>(key: string) {
- const storage = await state().then((x) => x.storage)
- const data = await storage.readToString(key + ".json")
- return JSON.parse(data) as T
+ return Bun.file(path.join(state().dir, key + ".json")).json() as Promise<T>
}
export async function writeJSON<T>(key: string, content: T) {
- const storage = await state().then((x) => x.storage)
- const json = JSON.stringify(content)
- await storage.write(key + ".json", json)
+ const target = path.join(state().dir, key + ".json")
+ const tmp = target + Date.now() + ".tmp"
+ await Bun.write(tmp, JSON.stringify(content))
+ await fs.rename(tmp, target).catch(() => {})
+ await fs.unlink(tmp).catch(() => {})
Bus.publish(Event.Write, { key, content })
}
+ const glob = new Bun.Glob("**/*")
export async function* list(prefix: string) {
try {
- const storage = await state().then((x) => x.storage)
- const list = storage.list(prefix)
- for await (const item of list) {
- yield item.path.slice(0, -5)
+ for await (const item of glob.scan({
+ cwd: path.join(state().dir, prefix),
+ onlyFiles: true,
+ })) {
+ const result = path.join(prefix, item.slice(0, -5))
+ yield result
}
} catch {
return
diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts
index a16bb2a89..62a32756c 100644
--- a/packages/opencode/src/tool/grep.ts
+++ b/packages/opencode/src/tool/grep.ts
@@ -11,7 +11,6 @@ export const GrepTool = Tool.define({
parameters: z.object({
pattern: z
.string()
- .nullable()
.describe("The regex pattern to search for in file contents"),
path: z
.string()
@@ -52,7 +51,7 @@ export const GrepTool = Tool.define({
if (exitCode === 1) {
return {
- metadata: { matches: 0, truncated: false },
+ metadata: { matches: 0, truncated: false, title: params.pattern },
output: "No files found",
}
}
@@ -94,7 +93,7 @@ export const GrepTool = Tool.define({
if (finalMatches.length === 0) {
return {
- metadata: { matches: 0, truncated: false },
+ metadata: { matches: 0, truncated: false, title: params.pattern },
output: "No files found",
}
}
diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts
index 34b7773c0..a7c55ac9f 100644
--- a/packages/opencode/src/tool/todo.ts
+++ b/packages/opencode/src/tool/todo.ts
@@ -34,7 +34,7 @@ export const TodoWriteTool = Tool.define({
return {
output: JSON.stringify(params.todos, null, 2),
metadata: {
- title: `${params.todos.length} todos`,
+ title: `${params.todos.filter((x) => x.status !== "completed").length} todos`,
todos: params.todos,
},
}
@@ -50,7 +50,7 @@ export const TodoReadTool = Tool.define({
return {
metadata: {
todos,
- title: ``,
+ title: `${todos.filter((x) => x.status !== "completed").length} todos`,
},
output: JSON.stringify(todos, null, 2),
}
diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts
index 5c25d7d02..8b905e53d 100644
--- a/packages/opencode/src/util/log.ts
+++ b/packages/opencode/src/util/log.ts
@@ -36,7 +36,7 @@ export namespace Log {
.filter((entry) => entry.isFile() && entry.name.endsWith(".log"))
.map((entry) => path.join(dir, entry.name))
- if (files.length <= 10) return
+ if (files.length <= 5) return
const filesToDelete = files.slice(0, -10)