diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 4558914cb7e..92fe6912531 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -92,7 +92,33 @@ export function Prompt(props: PromptProps) { let promptPartTypeId: number sdk.event.on(TuiEvent.PromptAppend.type, (evt) => { + const oldLength = input.plainText.length input.insertText(evt.properties.text) + + if (evt.properties.parts.length > 0) { + for (const part of evt.properties.parts) { + const extmarkId = + part.source && + input.extmarks.create({ + start: oldLength + (part.type === "file" ? part.source.text.start : part.source.start), + end: oldLength + (part.type === "file" ? part.source.text.end : part.source.end), + virtual: true, + styleId: part.type === "agent" ? agentStyleId : fileStyleId, + typeId: promptPartTypeId, + }) + + setStore( + produce((draft) => { + const partIndex = draft.prompt.parts.length + draft.prompt.parts.push(part) + if (extmarkId !== undefined) { + draft.extmarkToPartIndex.set(extmarkId, partIndex) + } + }), + ) + } + } + setTimeout(() => { input.getLayoutNode().markDirty() input.gotoBufferEnd() diff --git a/packages/opencode/src/cli/cmd/tui/event.ts b/packages/opencode/src/cli/cmd/tui/event.ts index 7c75523c136..52ad5fc89f1 100644 --- a/packages/opencode/src/cli/cmd/tui/event.ts +++ b/packages/opencode/src/cli/cmd/tui/event.ts @@ -1,9 +1,21 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" +import { MessageV2 } from "@/session/message-v2" import z from "zod" export const TuiEvent = { - PromptAppend: BusEvent.define("tui.prompt.append", z.object({ text: z.string() })), + PromptAppend: BusEvent.define( + "tui.prompt.append", + z.object({ + text: z.string(), + parts: z.array( + z.union([ + MessageV2.AgentPart.omit({ id: true, sessionID: true, messageID: true }), + MessageV2.FilePart.omit({ id: true, sessionID: true, messageID: true }), + ]), + ), + }), + ), CommandExecute: BusEvent.define( "tui.command.execute", z.object({ diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 615d9272866..a00dc13bfd9 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -2442,10 +2442,25 @@ export namespace Server { ...errors(400), }, }), - validator("json", TuiEvent.PromptAppend.properties), + validator("json", z.object({ text: z.string() })), async (c) => { - await Bus.publish(TuiEvent.PromptAppend, c.req.valid("json")) - return c.json(true) + const { text } = c.req.valid("json") + + try { + await Bus.publish(TuiEvent.PromptAppend, { + text, + parts: (await SessionPrompt.resolvePromptParts(text)).filter( + (part) => part.type === "agent" || part.type === "file", + ), + }) + return c.json(true) + } catch (error) { + log.error("Failed to process prompt append", { + text, + error: error instanceof Error ? error.message : String(error), + }) + return c.json(false) + } }, ) .post( diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index f891612272c..3ebad07c206 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -185,6 +185,7 @@ export namespace SessionPrompt { text: template, }, ] + const files = ConfigMarkdown.files(template) const seen = new Set() await Promise.all( @@ -201,28 +202,32 @@ export namespace SessionPrompt { const agent = await Agent.get(name) if (agent) { parts.push({ - type: "agent", + type: "agent" as const, name: agent.name, + source: { + start: match.index!, + end: match.index! + match[0].length, + value: match[0], + }, }) } return } - if (stats.isDirectory()) { - parts.push({ - type: "file", - url: `file://${filepath}`, - filename: name, - mime: "application/x-directory", - }) - return - } - parts.push({ - type: "file", - url: `file://${filepath}`, + type: "file" as const, + mime: stats.isDirectory() ? "application/x-directory" : "text/plain", filename: name, - mime: "text/plain", + url: `file://${filepath}`, + source: { + type: "file" as const, + path: name, + text: { + start: match.index!, + end: match.index! + match[0].length, + value: match[0], + }, + }, }) }), ) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 32f33f66219..ba0a814e474 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -615,6 +615,24 @@ export type EventTuiPromptAppend = { type: "tui.prompt.append" properties: { text: string + parts: Array< + | { + type: "agent" + name: string + source?: { + value: string + start: number + end: number + } + } + | { + type: "file" + mime: string + filename?: string + url: string + source?: FilePartSource + } + > } }