fix(tui): streamline generic tool rendering (#43158)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot] 2026-08-17 21:04:07 -05:00 committed by GitHub
parent 5252cefab2
commit a1eca087d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 35 deletions

View file

@ -2595,58 +2595,61 @@ type ToolProps = {
}
function GenericTool(props: ToolProps) {
const theme = useTheme()
const { currentSyntax: syntax } = useThemes()
const output = createMemo(() => props.output?.trim() ?? "")
const args = createMemo(() => JSON.stringify(props.input, null, 2))
const input = createMemo(() => Object.entries(props.input))
const [expanded, setExpanded] = createSignal(false)
const expandable = createMemo(() => Object.keys(props.input).length > 0 || output().length > 0)
const expandable = createMemo(() => input().length > 0 || output().length > 0)
const loading = createMemo(() => props.part.state.status === "streaming" || props.part.state.status === "running")
return (
<BlockTool
title={`${props.tool}`}
part={props.part}
spinner={props.part.state.status === "streaming" || props.part.state.status === "running"}
onClick={expandable() ? () => setExpanded((value) => !value) : undefined}
>
<>
<InlineTool
icon={props.part.state.status === "error" ? "✗" : "✓"}
complete={props.part.state.status === "completed"}
pending={props.tool}
spinner={loading()}
part={props.part}
onClick={expandable() ? () => setExpanded((value) => !value) : undefined}
>
{genericToolSummary(props.tool, props.input)}
</InlineTool>
<Show when={expanded()}>
<box gap={1} paddingTop={1}>
<Show when={Object.keys(props.input).length > 0}>
<box gap={1}>
<text>
<span style={{ bg: theme.raise(theme.background.default), fg: theme.text.subdued }}> Input </span>
</text>
<box paddingLeft={1}>
<code
content={args()}
filetype="json"
syntaxStyle={syntax()}
conceal={false}
drawUnstyledText={false}
fg={theme.text.default}
/>
<box paddingLeft={3 + INLINE_TOOL_ICON_WIDTH}>
<For each={input()}>
{([key, value]) => (
<box flexDirection="row">
<text flexShrink={0} fg={theme.text.subdued}>
{key}:{" "}
</text>
<text flexGrow={1} wrapMode="word" fg={theme.text.default}>
{typeof value === "string" ? value : JSON.stringify(value, null, 2)}
</text>
</box>
</box>
</Show>
)}
</For>
<Show when={output()}>
{(value) => (
<box gap={1}>
<text>
<span style={{ bg: theme.raise(theme.background.default), fg: theme.text.subdued }}> Output </span>
<box flexDirection="row">
<text flexShrink={0} fg={theme.text.subdued}>
output:{" "}
</text>
<text flexGrow={1} fg={theme.text.default} wrapMode="word">
{value()}
</text>
<box paddingLeft={1}>
<text fg={theme.text.default} wrapMode="word">
{value()}
</text>
</box>
</box>
)}
</Show>
</box>
</Show>
</BlockTool>
</>
)
}
export function genericToolSummary(tool: string, input: Record<string, unknown>) {
const args = primitiveInputSummary(input).replace(/\s+/g, " ")
return `${tool}${args ? ` ${args}` : ""}`
}
function useToolPermission(part: () => SessionMessageAssistantTool | undefined) {
const ctx = use()
const data = useData()

View file

@ -4,6 +4,7 @@ import { testRender, type JSX } from "@opentui/solid"
import {
InlineToolRow,
executeCallSummary,
genericToolSummary,
isBackgroundSubagent,
parseApplyPatchFiles,
parseDiagnostics,
@ -199,6 +200,21 @@ describe("TUI inline tool wrapping", () => {
).toBe("↳ session.prompt [text=first line second line]")
})
test("summarizes generic tool arguments on one line", () => {
expect(
genericToolSummary("demo_search_catalog", {
query: "wireless keyboard",
limit: 8,
includeArchived: false,
filters: { category: "accessories" },
}),
).toBe("demo_search_catalog [query=wireless keyboard, limit=8, includeArchived=false]")
expect(genericToolSummary("demo_get_weather", { city: "Tokyo", units: "celsius" })).toBe(
"demo_get_weather [city=Tokyo, units=celsius]",
)
expect(genericToolSummary("demo_refresh", {})).toBe("demo_refresh")
})
test("ignores diagnostics with malformed nested ranges", () => {
expect(
parseDiagnostics(