diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index fad7f84984..476b52a5e0 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -553,11 +553,10 @@ export function Autocomplete(props: { const commands = createMemo((): AutocompleteOption[] => { const results: AutocompleteOption[] = slashes().map((command) => { - const name = command.display.trim().slice(1) - if (name !== "compact") return command + if (!command.input) return command return { ...command, - onSelect: () => insertSlashCommand(name), + onSelect: () => insertSlashCommand(command.name), } }) 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 9764439ec0..05caf1482b 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -1132,6 +1132,8 @@ export function Prompt(props: PromptProps) { ] : [] + const slash = parseSlashCommand(inputText) + if (store.mode === "shell") { void sdk.client.session.shell({ sessionID, @@ -1143,34 +1145,20 @@ export function Prompt(props: PromptProps) { command: inputText, }) setStore("mode", "normal") - } else if (isCompactSlash(inputText)) { - const instructions = slashArguments(inputText).trim() - const payload: Parameters[0] & { $body_instructions?: string } = { + } else if (slash && (slash.name === "compact" || slash.name === "summarize")) { + const instructions = slash.arguments.trim() + const payload = { sessionID, modelID: selectedModel.modelID, providerID: selectedModel.providerID, ...(instructions ? { $body_instructions: instructions } : {}), - } + } satisfies Parameters[0] & { $body_instructions?: string } void sdk.client.session.summarize(payload) - } else if ( - inputText.startsWith("/") && - iife(() => { - const firstLine = inputText.split("\n")[0] - const command = firstLine.split(" ")[0].slice(1) - return sync.data.command.some((x) => x.name === command) - }) - ) { - // Parse command from first line, preserve multi-line content in arguments - const firstLineEnd = inputText.indexOf("\n") - const firstLine = firstLineEnd === -1 ? inputText : inputText.slice(0, firstLineEnd) - const [command, ...firstLineArgs] = firstLine.split(" ") - const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1) - const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "") - + } else if (slash && sync.data.command.some((x) => x.name === slash.name)) { void sdk.client.session.command({ sessionID, - command: command.slice(1), - arguments: args, + command: slash.name, + arguments: slash.arguments, agent: agent.name, model: `${selectedModel.providerID}/${selectedModel.modelID}`, messageID, @@ -1230,17 +1218,16 @@ export function Prompt(props: PromptProps) { return true } - function isCompactSlash(text: string) { - const command = text.split("\n")[0].split(" ")[0].slice(1) - return text.startsWith("/") && (command === "compact" || command === "summarize") - } - - function slashArguments(text: string) { + function parseSlashCommand(text: string) { + if (!text.startsWith("/")) return const firstLineEnd = text.indexOf("\n") const firstLine = firstLineEnd === -1 ? text : text.slice(0, firstLineEnd) - const [, ...firstLineArgs] = firstLine.split(" ") + const [command, ...firstLineArgs] = firstLine.split(" ") const restOfInput = firstLineEnd === -1 ? "" : text.slice(firstLineEnd + 1) - return firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "") + return { + name: command.slice(1), + arguments: firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : ""), + } } const exit = useExit() diff --git a/packages/opencode/src/cli/cmd/tui/keymap.tsx b/packages/opencode/src/cli/cmd/tui/keymap.tsx index d8489fd2f7..eea0ff74cb 100644 --- a/packages/opencode/src/cli/cmd/tui/keymap.tsx +++ b/packages/opencode/src/cli/cmd/tui/keymap.tsx @@ -25,9 +25,11 @@ export { useBindings, useKeymapSelector } export type OpenTuiKeymap = ReturnType type OpencodeModeStack = ReturnType type CommandSlashEntry = { + name: string display: string description?: string aliases?: string[] + input?: boolean onSelect: () => void } type Command = ReturnType[number] @@ -256,6 +258,7 @@ export function useCommandSlashes(): Accessor { if (typeof slashName !== "string" || !slashName) return [] const slashAliases = entry.command.slashAliases return { + name: slashName, display: `/${slashName}`, description: typeof entry.command.desc === "string" @@ -266,6 +269,7 @@ export function useCommandSlashes(): Accessor { aliases: Array.isArray(slashAliases) ? slashAliases.filter((alias): alias is string => typeof alias === "string").map((alias) => `/${alias}`) : undefined, + input: entry.command.slashInput === true, onSelect: () => keymap.dispatchCommand(entry.command.name), } }), diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 245a7296b5..857e9c0868 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -546,6 +546,7 @@ export function Session() { slash: { name: "compact", aliases: ["summarize"], + input: true, }, run: () => { const selectedModel = local.model.current() @@ -1046,6 +1047,7 @@ export function Session() { desc: "description" in command ? command.description : undefined, slashName: "slash" in command ? command.slash?.name : undefined, slashAliases: "slash" in command ? command.slash?.aliases : undefined, + slashInput: "slash" in command ? command.slash?.input : undefined, ...command, })), )