opencode/packages/tui/test/mini/entry.body.test.ts
2026-07-21 22:01:09 +02:00

555 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, test } from "bun:test"
import type { SessionMessageAssistantTool } from "@opencode-ai/client/promise"
import { entryBody, entryCanStream, entryDone } from "../../src/mini/entry.body"
import type { StreamCommit, ToolSnapshot } from "../../src/mini/types"
import { canonicalToolPart } from "./fixture/tool-part"
function commit(input: Partial<StreamCommit> & Pick<StreamCommit, "kind" | "text" | "phase" | "source">): StreamCommit {
return input
}
function toolCommit(input: {
tool: string
state: SessionMessageAssistantTool["state"]
phase?: StreamCommit["phase"]
toolState?: StreamCommit["toolState"]
text?: string
id?: string
messageID?: string
}) {
return commit({
kind: "tool",
text: input.text ?? "",
phase: input.phase ?? "final",
source: "tool",
tool: input.tool,
toolState:
input.toolState ??
(input.state.status === "error" ? "error" : input.state.status === "completed" ? "completed" : "running"),
messageID: input.messageID,
part: canonicalToolPart(input.tool, input.state, input.id),
})
}
function structured(next: StreamCommit) {
const body = entryBody(next)
expect(body.type).toBe("structured")
if (body.type !== "structured") {
throw new Error("expected structured body")
}
return body.snapshot
}
describe("run entry body", () => {
test("renders a failed direct shell as an error instead of completed success", () => {
const failed = commit({
kind: "tool",
text: "Shell exited with code 7",
phase: "final",
source: "tool",
tool: "shell",
toolState: "error",
toolError: "Shell → exited · code 7",
shell: { command: "false" },
})
expect(entryBody(failed)).toEqual({ type: "text", content: "✖ shell failed: Shell → exited · code 7" })
expect(entryBody(failed, { mono: true })).toEqual({
type: "text",
content: "! shell failed: Shell → exited · code 7",
})
})
test("renders assistant, reasoning, and user entries in their display formats", () => {
expect(
entryBody(
commit({
kind: "assistant",
text: "# Title\n\nHello **world**",
phase: "progress",
source: "assistant",
partID: "part-1",
}),
),
).toEqual({
type: "markdown",
content: "# Title\n\nHello **world**",
})
const reasoning = entryBody(
commit({
kind: "reasoning",
text: "Thinking: plan next steps",
phase: "progress",
source: "reasoning",
partID: "reason-1",
}),
)
expect(reasoning).toEqual({
type: "code",
filetype: "markdown",
content: "_Thinking:_ plan next steps",
})
expect(
entryCanStream(
commit({
kind: "reasoning",
text: "Thinking: plan next steps",
phase: "progress",
source: "reasoning",
}),
reasoning,
),
).toBe(true)
expect(
entryBody(
commit({
kind: "user",
text: "Inspect footer tabs",
phase: "start",
source: "system",
}),
),
).toEqual({
type: "text",
content: " Inspect footer tabs",
})
expect(
entryBody(commit({ kind: "user", text: "Inspect footer tabs", phase: "start", source: "system" }), {
mono: true,
}),
).toEqual({ type: "text", content: "> Inspect footer tabs" })
})
for (const item of [
{
name: "keeps completed write tool finals structured",
commit: toolCommit({
tool: "write",
state: {
status: "completed",
input: {
path: "src/a.ts",
content: "const x = 1\n",
},
structured: {},
content: [],
},
}),
snapshot: {
kind: "code",
title: "# Wrote src/a.ts",
content: "const x = 1\n",
file: "src/a.ts",
},
},
{
name: "keeps completed edit tool finals structured",
commit: toolCommit({
tool: "edit",
state: {
status: "completed",
input: {
path: "src/a.ts",
},
structured: {
files: [{ file: "src/a.ts", status: "modified", patch: "@@ -1 +1 @@\n-old\n+new\n" }],
},
content: [],
},
}),
snapshot: {
kind: "diff",
items: [
{
title: "# Edited src/a.ts",
diff: "@@ -1 +1 @@\n-old\n+new\n",
file: "src/a.ts",
},
],
},
},
{
name: "keeps completed patch tool finals structured",
commit: toolCommit({
tool: "patch",
state: {
status: "completed",
input: {},
content: [],
structured: {
files: [
{
status: "modified",
file: "src/a.ts",
patch: "@@ -1 +1 @@\n-old\n+new\n",
},
],
},
},
}),
snapshot: {
kind: "diff",
items: [
{
title: "# Patched src/a.ts",
diff: "@@ -1 +1 @@\n-old\n+new\n",
file: "src/a.ts",
deletions: 0,
},
],
},
},
] satisfies Array<{ name: string; commit: StreamCommit; snapshot: ToolSnapshot }>) {
test(item.name, () => {
expect(structured(item.commit)).toEqual(item.snapshot)
})
}
test("keeps running subagent tool state out of scrollback", () => {
expect(
entryBody(
toolCommit({
tool: "subagent",
phase: "start",
toolState: "running",
text: "running inspect reducer",
state: {
status: "running",
input: {
description: "Inspect reducer",
agent: "explore",
},
structured: { sessionID: "ses-child-1", status: "running" },
content: [],
},
}),
),
).toEqual({
type: "none",
})
})
test("promotes subagent results to markdown and falls back to structured summaries", () => {
expect(
entryBody(
toolCommit({
tool: "subagent",
state: {
status: "completed",
input: {
description: "Inspect reducer",
agent: "explore",
},
content: [{ type: "text", text: "# Findings\n\n- Footer stays live" }],
structured: {
sessionID: "ses-child-1",
status: "completed",
output: "# Findings\n\n- Footer stays live",
},
},
}),
),
).toEqual({
type: "markdown",
content: "# Findings\n\n- Footer stays live",
})
expect(
structured(
toolCommit({
tool: "subagent",
state: {
status: "completed",
input: {
description: "Inspect reducer",
agent: "explore",
},
content: [],
structured: {
sessionID: "ses-child-1",
status: "completed",
output: "",
},
},
}),
),
).toEqual({
kind: "task",
title: "# Explore Subagent",
rows: ["Inspect reducer"],
tail: "",
})
})
test("streams tool progress text and treats completed progress as done", () => {
const body = entryBody(
commit({
kind: "tool",
text: "partial output",
phase: "progress",
source: "tool",
tool: "shell",
partID: "tool-2",
}),
)
expect(body).toEqual({
type: "text",
content: "partial output",
})
expect(
entryCanStream(
commit({
kind: "tool",
text: "partial output",
phase: "progress",
source: "tool",
tool: "shell",
}),
body,
),
).toBe(true)
expect(
entryDone(
commit({
kind: "tool",
text: "output",
phase: "progress",
source: "tool",
tool: "shell",
toolState: "completed",
}),
),
).toBe(true)
})
test("formats completed shell output with a blank line after the command and no trailing blank row", () => {
const output = ["/tmp/demo", "git status", "On branch demo", "nothing to commit, working tree clean", ""].join("\n")
expect(
entryBody(
toolCommit({
tool: "shell",
phase: "progress",
toolState: "completed",
text: output,
state: {
status: "completed",
input: {
command: "git status",
workdir: "/tmp/demo",
},
content: [{ type: "text", text: output }],
structured: { exit: 0, truncated: false },
},
}),
),
).toEqual({
type: "text",
content: "\nOn branch demo\nnothing to commit, working tree clean",
})
})
test("renders command-only shell starts without the shell header", () => {
expect(
entryBody(
toolCommit({
tool: "shell",
phase: "start",
toolState: "running",
text: "running shell",
state: {
status: "running",
input: {
command: "ls",
},
structured: {},
content: [],
},
}),
),
).toEqual({
type: "text",
content: "$ ls",
})
})
test("renders direct shell commits without a synthetic shell header", () => {
expect(
entryBody(
commit({
kind: "tool",
text: "running shell",
phase: "start",
source: "tool",
tool: "shell",
partID: "shell:call-1",
toolState: "running",
shell: {
command: "pwd",
},
}),
),
).toEqual({
type: "text",
content: "$ pwd",
})
expect(
entryBody(
commit({
kind: "tool",
text: "/tmp/demo\n",
phase: "progress",
source: "tool",
tool: "shell",
partID: "shell:call-1",
toolState: "completed",
shell: {
command: "pwd",
},
}),
),
).toEqual({
type: "text",
content: "\n/tmp/demo",
})
})
test("hides shell tool output but not direct shell output", () => {
const output = commit({ kind: "tool", text: "output", phase: "progress", source: "tool", tool: "shell" })
expect(entryBody(output, { shellOutput: false })).toEqual({ type: "none" })
expect(entryBody({ ...output, shell: { command: "pwd" } }, { shellOutput: false })).toEqual({
type: "text",
content: "\noutput",
})
})
test("falls back to patch summary when patch has no visible diff items", () => {
expect(
entryBody(
toolCommit({
tool: "patch",
state: {
status: "completed",
input: {
patchText: "*** Begin Patch\n*** End Patch",
},
content: [],
structured: {
files: [
{
status: "modified",
file: "src/a.ts",
},
],
},
},
}),
),
).toEqual({
type: "text",
content: "~ Patched src/a.ts",
})
})
test("suppresses redundant patched rows when patch also created a file", () => {
expect(
entryBody(
toolCommit({
tool: "patch",
state: {
status: "completed",
input: {
patchText: "*** Begin Patch\n*** End Patch",
},
content: [],
structured: {
files: [
{
status: "modified",
file: "src/a.ts",
},
{
status: "added",
file: "README-demo.md",
},
],
},
},
}),
),
).toEqual({
type: "text",
content: "+ Created README-demo.md",
})
})
test("renders glob failures as the raw error under the existing header", () => {
expect(
entryBody(
toolCommit({
tool: "glob",
phase: "final",
toolState: "error",
state: {
status: "error",
input: {
pattern: "**/*tool*",
path: "/tmp/demo/run",
},
error: { type: "unknown", message: "No such file or directory: '/tmp/demo/run'" },
structured: {},
content: [],
},
}),
),
).toEqual({
type: "text",
content: "No such file or directory: '/tmp/demo/run'",
})
})
test("renders bounded structured output for completed unknown tools without text", () => {
const body = entryBody(
toolCommit({
tool: "mcp_custom",
phase: "final",
toolState: "completed",
text: "",
state: {
status: "completed",
input: { target: "demo" },
structured: {
result: { ok: true, nested: { values: Array.from({ length: 40 }, (_, index) => ({ index })) } },
large: "x".repeat(8_000),
},
content: [],
},
}),
)
expect(body).toMatchObject({ type: "code", filetype: "json" })
expect(body.type === "code" ? body.content : "").toContain('"ok": true')
expect(body.type === "code" ? body.content : "").toContain("[truncated]")
expect(body.type === "code" ? body.content.length : Infinity).toBeLessThanOrEqual(4_096)
})
test("renders interrupted assistant finals as text", () => {
expect(
entryBody(
commit({
kind: "assistant",
text: "",
phase: "final",
source: "assistant",
interrupted: true,
partID: "part-1",
}),
),
).toEqual({
type: "text",
content: "assistant interrupted",
})
})
})