Skip to content

Commit cf6e5b3

Browse files
authored
mini: fix shell tool output display (#37711)
1 parent cbcf191 commit cf6e5b3

4 files changed

Lines changed: 29 additions & 10 deletions

File tree

packages/cli/src/mini/noninteractive.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/p
22
import { SessionMessage } from "@opencode-ai/schema/session-message"
33
import { EOL } from "node:os"
44
import { readFile } from "node:fs/promises"
5+
import { toolOutputText } from "./tool"
56
import { UI } from "./ui"
67
import type { MiniToolPart } from "./types"
78

@@ -274,10 +275,7 @@ export async function runNonInteractivePrompt(input: Input) {
274275
state: {
275276
status: "completed",
276277
input: current.input,
277-
output: event.data.content
278-
.filter((item) => item.type === "text")
279-
.map((item) => item.text)
280-
.join("\n"),
278+
output: toolOutputText(current.tool, event.data.content),
281279
title: current.tool,
282280
metadata: {
283281
structured: event.data.structured,

packages/cli/src/mini/stream-v2.subagent.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
} from "@opencode-ai/client/promise"
2424
import { Locale } from "@opencode-ai/tui/util/locale"
2525
import type { FooterSubagentDetail, FooterSubagentState, FooterSubagentTab, MiniToolPart, StreamCommit } from "./types"
26+
import { toolOutputText } from "./tool"
2627

2728
const CHILD_MESSAGE_LIMIT = 80
2829
const CHILD_FRAME_LIMIT = 80
@@ -32,10 +33,6 @@ const FALLBACK_LABEL = "Subagent"
3233

3334
type V2Event = EventSubscribeOutput
3435

35-
export function outputText(content: ReadonlyArray<{ type: string; text?: string }>) {
36-
return content.flatMap((item) => (item.type === "text" && item.text ? [item.text] : [])).join("\n")
37-
}
38-
3936
export function miniTool(input: {
4037
sessionID: string
4138
messageID: string
@@ -82,7 +79,7 @@ export function miniTool(input: {
8279
state: {
8380
status: "completed",
8481
input: tool.state.input,
85-
output: outputText(tool.state.content),
82+
output: toolOutputText(tool.name, tool.state.content),
8683
title: tool.name,
8784
metadata: {
8885
structured: tool.state.structured,

packages/cli/src/mini/tool.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ function text(v: unknown): string {
177177
return typeof v === "string" ? v : ""
178178
}
179179

180+
export function toolOutputText(name: string, content: ReadonlyArray<{ type: string; text?: string }>) {
181+
// V2 shell content appends model-only status after the user-visible command output.
182+
if (name === "shell") return content.find((item) => item.type === "text")?.text ?? ""
183+
return content.flatMap((item) => (item.type === "text" && item.text ? [item.text] : [])).join("\n")
184+
}
185+
180186
function num(v: unknown): number | undefined {
181187
if (typeof v !== "number" || !Number.isFinite(v)) {
182188
return undefined

packages/cli/test/mini.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
22
import { InstallationVersion } from "@opencode-ai/core/installation/version"
33
import path from "node:path"
44
import { mergeInteractiveInput, mergeNonInteractiveInput, parseRunModel, pickRunModel } from "../src/mini"
5-
import { toolInlineInfo, toolView } from "../src/mini/tool"
5+
import { toolInlineInfo, toolOutputText, toolView } from "../src/mini/tool"
66

77
async function cli(args: string[]) {
88
const child = Bun.spawn([process.execPath, "run", "src/index.ts", ...args], {
@@ -36,6 +36,24 @@ describe("mini command", () => {
3636
expect(toolInlineInfo(part)).toMatchObject({ icon: "$", title: "pwd", mode: "block" })
3737
})
3838

39+
test("uses non-empty V2 shell output without the model-facing status", () => {
40+
expect(
41+
toolOutputText("shell", [
42+
{ type: "text", text: "mini-output\n" },
43+
{ type: "text", text: "Command exited with code 0." },
44+
]),
45+
).toBe("mini-output\n")
46+
})
47+
48+
test("keeps empty V2 shell output empty", () => {
49+
expect(
50+
toolOutputText("shell", [
51+
{ type: "text", text: "" },
52+
{ type: "text", text: "Command exited with code 0." },
53+
]),
54+
).toBe("")
55+
})
56+
3957
test("uses piped stdin as the initial prompt", () => {
4058
expect(mergeInteractiveInput("from stdin", undefined)).toBe("from stdin")
4159
expect(mergeInteractiveInput("from stdin", "from flag")).toBe("from stdin\nfrom flag")

0 commit comments

Comments
 (0)