Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions packages/ui/src/components/message-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ function DeleteUpToIcon() {
)
}

const TOOL_ICON = "🔧"
const USER_BORDER_COLOR = "var(--message-user-border)"
const ASSISTANT_BORDER_COLOR = "var(--message-assistant-border)"
const TOOL_BORDER_COLOR = "var(--message-tool-border)"
Expand Down Expand Up @@ -612,8 +611,6 @@ function ToolCallItem(props: ToolCallItemProps) {
/>
</Show>

<span class="tool-call-icon">{TOOL_ICON}</span>
<span>{t("messageBlock.tool.header")}</span>
<span class="tool-name">{toolName() || t("messageBlock.tool.unknown")}</span>
</div>

Expand Down
33 changes: 22 additions & 11 deletions packages/ui/src/components/tool-call.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
ToolScrollHelpers,
} from "./tool-call/types"
import {
buildToolPreview,
buildToolSpeechText,
ensureMarkdownContent,
getRelativePath,
Expand Down Expand Up @@ -606,7 +607,7 @@ export default function ToolCall(props: ToolCallProps) {
return undefined
})

const toolOutputDefaultExpanded = createMemo(() => (preferences().toolOutputExpansion || "expanded") === "expanded")
const toolOutputDefaultExpanded = createMemo(() => (preferences().toolOutputExpansion || "collapsed") === "expanded")
const diagnosticsDefaultExpanded = createMemo(() => (preferences().diagnosticsExpansion || "expanded") === "expanded")

const defaultExpandedForTool = createMemo(() => {
Expand Down Expand Up @@ -678,6 +679,7 @@ export default function ToolCall(props: ToolCallProps) {
const [toolCallRootEl, setToolCallRootEl] = createSignal<HTMLDivElement | undefined>()
const [scrollTopSnapshot, setScrollTopSnapshot] = createSignal(0)
const [diagnosticsOverride, setDiagnosticsOverride] = createSignal<boolean | undefined>(undefined)
const collapsedPreview = createMemo(() => buildToolPreview(toolName(), toolState()))

const diagnosticsExpanded = () => {
if (isPermissionActive() || isQuestionActive()) return true
Expand Down Expand Up @@ -864,19 +866,18 @@ export default function ToolCall(props: ToolCallProps) {
const status = () => toolState()?.status || ""

return (
<div

ref={(element) => {
<div
ref={(element) => {
setToolCallRootEl(element || undefined)
}}
class={`tool-call ${combinedStatusClass()}`}
data-part-type="tool"
data-tool-name={toolName()}
data-instance-id={props.instanceId}
data-session-id={props.sessionId}
data-message-id={props.messageId}
data-part-id={toolCallIdentifier()}
>
data-session-id={props.sessionId}
data-message-id={props.messageId}
data-part-id={toolCallIdentifier()}
>
<div class="tool-call-header" data-action-overflow={actionMenuItems().length > 1 ? "true" : undefined}>
<button
type="button"
Expand All @@ -885,7 +886,7 @@ export default function ToolCall(props: ToolCallProps) {
aria-expanded={expanded()}
>
<span class="tool-call-summary" data-tool-icon={getToolIcon(toolName())}>
{headerText()}
<span class="tool-call-summary-text">{headerText()}</span>
</span>
</button>

Expand Down Expand Up @@ -969,9 +970,19 @@ export default function ToolCall(props: ToolCallProps) {
setScrollTopSnapshot={setScrollTopSnapshot}
/>
</Show>

<Show when={diagnosticsEntries().length}>

<Show when={!expanded() && collapsedPreview()}>
{(preview) => (
<div class="tool-call-preview">
<div class="tool-call-preview-label">
{preview().kind === "output" ? t("toolCall.io.output") : t("toolCall.io.input")}
</div>
<div class="tool-call-preview-text">{preview().text}</div>
</div>
)}
</Show>

<Show when={diagnosticsEntries().length}>
{renderDiagnosticsSection(
t,
diagnosticsEntries(),
Expand Down
91 changes: 91 additions & 0 deletions packages/ui/src/components/tool-call/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,97 @@ export function formatUnknown(value: unknown): { text: string; language?: string
return null
}

function joinPreviewParts(...parts: Array<string | undefined>): string | null {
const text = parts
.map((part) => (typeof part === "string" ? part.trim() : ""))
.filter(Boolean)
.join("\n")

return text.length > 0 ? text : null
}

function getToolInputPreview(toolName: string, input: Record<string, any>): unknown {
switch (toolName) {
case "bash":
return joinPreviewParts(
typeof input.command === "string" ? `$ ${input.command}` : undefined,
typeof input.description === "string" ? input.description : undefined,
)
case "read":
case "edit":
case "write":
case "patch":
case "apply_patch":
case "list":
return typeof input.filePath === "string"
? input.filePath
: typeof input.path === "string"
? input.path
: input
case "glob":
return joinPreviewParts(
typeof input.pattern === "string" ? input.pattern : undefined,
typeof input.path === "string" ? input.path : undefined,
)
case "grep":
return joinPreviewParts(
typeof input.pattern === "string" ? input.pattern : undefined,
typeof input.include === "string" ? input.include : undefined,
typeof input.path === "string" ? input.path : undefined,
)
case "webfetch":
return typeof input.url === "string" ? input.url : input
case "task":
return joinPreviewParts(
typeof input.description === "string" ? input.description : undefined,
typeof input.prompt === "string" ? input.prompt : undefined,
)
default:
return input
}
}

function formatPreviewText(value: unknown): string | null {
if (Array.isArray(value) && value.length === 0) {
return null
}

if (value && typeof value === "object" && Object.keys(value as Record<string, unknown>).length === 0) {
return null
}

const formatted = formatUnknown(value)
const text = formatted?.text?.trim()
return text ? text : null
}

export function buildToolPreview(toolName: string, state?: ToolState): {
kind: "input" | "output"
text: string
} | null {
const { input, metadata } = readToolStatePayload(state)

const outputCandidate = !state
? undefined
: isToolStateCompleted(state)
? state.output ?? metadata.output ?? metadata.diff ?? metadata.preview
: isToolStateRunning(state) || isToolStateError(state)
? metadata.output ?? metadata.diff ?? metadata.preview
: metadata.preview ?? metadata.diff

const outputText = formatPreviewText(outputCandidate)
if (outputText) {
return { kind: "output", text: outputText }
}

const inputText = formatPreviewText(getToolInputPreview(toolName, input))
if (inputText) {
return { kind: "input", text: inputText }
}

return null
}

export function inferLanguageFromPath(path?: string): string | undefined {
return getLanguageFromPath(path || "")
}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/lib/settings/behavior-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export function getBehaviorSettings(actions: BehaviorRegistryActions): BehaviorS
id: "behavior.toolOutputsDefault",
titleKey: "settings.behavior.toolOutputsDefault.title",
subtitleKey: "settings.behavior.toolOutputsDefault.subtitle",
get: (p) => (p.toolOutputExpansion ?? "expanded") as ExpansionPreference,
get: (p) => (p.toolOutputExpansion ?? "collapsed") as ExpansionPreference,
set: (next) => {
if (updatePreferences) {
updatePreferences({ toolOutputExpansion: next as ExpansionPreference })
Expand Down Expand Up @@ -418,15 +418,15 @@ export function getBehaviorCommands(actions: BehaviorRegistryActions): Command[]
{
id: "tool-output-default-visibility",
label: () => {
const mode = actions.preferences().toolOutputExpansion || "expanded"
const mode = actions.preferences().toolOutputExpansion || "collapsed"
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
return tGlobal("commands.toolOutputsDefault.label", { state })
},
description: () => tGlobal("commands.toolOutputsDefault.description"),
category: "System",
keywords: () => splitKeywords("commands.toolOutputsDefault.keywords"),
action: () => {
const mode = actions.preferences().toolOutputExpansion || "expanded"
const mode = actions.preferences().toolOutputExpansion || "collapsed"
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
actions.setToolOutputExpansion(next)
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/stores/preferences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const defaultUiSettings: UiSettings = {
promptSubmitOnEnter: false,
showPromptVoiceInput: true,
diffViewMode: "split",
toolOutputExpansion: "expanded",
toolOutputExpansion: "collapsed",
diagnosticsExpansion: "expanded",
toolInputsVisibility: "collapsed",
showUsageMetrics: true,
Expand Down
17 changes: 12 additions & 5 deletions packages/ui/src/styles/messaging/tool-call.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

.tool-call-header-meta {
@apply flex items-center gap-2;
min-width: 0;
}

.tool-call-header-button {
Expand Down Expand Up @@ -61,10 +62,6 @@
display: inline-flex;
}

.tool-call-header-label .tool-call-icon {
@apply text-base;
}

.tool-call-header-label .tool-name {
font-family: var(--font-family-mono);
color: inherit;
Expand Down Expand Up @@ -99,6 +96,7 @@
background-color: transparent;
color: var(--text-primary);
border-bottom: 1px solid var(--tool-call-border-color);
min-width: 0;
}

.tool-call-header:hover {
Expand All @@ -112,6 +110,7 @@
border-radius: 0;
color: var(--text-primary);
flex: 1;
min-width: 0;
}

.tool-call-header-toggle::before {
Expand Down Expand Up @@ -186,6 +185,14 @@
.tool-call-summary {
@apply flex-1 text-start inline-flex items-center gap-2;
color: var(--text-primary);
min-width: 0;
}

.tool-call-summary-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.tool-call-summary::before {
Expand Down Expand Up @@ -247,7 +254,7 @@
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
max-height: var(--tool-call-max-height-compact, calc(25 * 1.4em));
max-height: calc(6 * var(--tool-call-line-unit));
overflow-y: scroll;
}

Expand Down
Loading