From 7505fa61b9caa17f1ef358961d7e46beb3276ca9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 13 Aug 2025 13:28:51 -0400 Subject: [PATCH] wip: bash commands --- packages/opencode/src/server/server.ts | 30 +++++++++ packages/opencode/src/session/index.ts | 92 ++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 79be29d2..ec17ff03 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -592,6 +592,36 @@ export namespace Server { return c.json(msg) }, ) + .post( + "/session/:id/command", + describeRoute({ + description: "Run a bash command", + operationId: "session.chat", + responses: { + 200: { + description: "Created message", + content: { + "application/json": { + schema: resolver(MessageV2.Assistant), + }, + }, + }, + }, + }), + zValidator( + "param", + z.object({ + id: z.string().openapi({ description: "Session ID" }), + }), + ), + zValidator("json", Session.CommandInput.omit({ sessionID: true })), + async (c) => { + const sessionID = c.req.valid("param").id + const body = c.req.valid("json") + const msg = await Session.command({ ...body, sessionID }) + return c.json(msg) + }, + ) .post( "/session/:id/revert", describeRoute({ diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index c0ab7c7d..0957d405 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -43,6 +43,8 @@ import { Plugin } from "../plugin" import { Agent } from "../agent/agent" import { Permission } from "../permission" import { Wildcard } from "../util/wildcard" +import { BashTool } from "../tool/bash" +import { ulid } from "ulid" export namespace Session { const log = Log.create({ service: "session" }) @@ -997,6 +999,96 @@ export namespace Session { return result } + export const CommandInput = z.object({ + sessionID: Identifier.schema("session"), + agent: z.string(), + command: z.string(), + }) + export type CommandInput = z.infer + export async function command(input: CommandInput) { + using abort = lock(input.sessionID) + const msg: MessageV2.Assistant = { + id: Identifier.ascending("message"), + sessionID: input.sessionID, + system: [], + mode: input.agent, + cost: 0, + path: { + cwd: App.info().path.cwd, + root: App.info().path.root, + }, + time: { + created: Date.now(), + }, + role: "assistant", + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "", + providerID: "", + } + await updateMessage(msg) + const part: MessageV2.Part = { + type: "tool", + id: Identifier.ascending("part"), + messageID: msg.id, + sessionID: input.sessionID, + tool: "bash", + callID: ulid(), + state: { + status: "running", + time: { + start: Date.now(), + }, + input: { + command: input.command, + }, + }, + } + await updatePart(part) + const tool = await BashTool.init() + const result = await tool.execute( + { + command: input.command, + description: "User command", + }, + { + messageID: msg.id, + sessionID: input.sessionID, + abort: abort.signal, + callID: part.callID, + agent: input.agent, + metadata: async (e) => { + if (part.state.status === "running") { + part.state.title = e.title + part.state.metadata = e.metadata + await updatePart(part) + } + }, + }, + ) + msg.time.completed = Date.now() + await updateMessage(msg) + if (part.state.status === "running") { + part.state = { + status: "completed", + time: { + ...part.state.time, + end: Date.now(), + }, + input: part.state.input, + title: result.title, + metadata: result.metadata, + output: result.output, + } + await updatePart(part) + } + return { info: msg, parts: [part] } + } + function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) { const toolcalls: Record = {} let snapshot: string | undefined