11import { createSignal , Show , createEffect , createMemo , onCleanup } from "solid-js"
2- import { Copy } from "lucide-solid"
2+ import { ArrowRightSquare , Copy } from "lucide-solid"
3+ import { stringify as stringifyYaml } from "yaml"
34import { messageStoreBus } from "../stores/message-v2/bus"
45import { useTheme } from "../lib/theme"
56import { useGlobalCache } from "../lib/hooks/use-global-cache"
@@ -27,7 +28,17 @@ import type {
2728 ToolRendererContext ,
2829 ToolScrollHelpers ,
2930} from "./tool-call/types"
30- import { getRelativePath , getToolIcon , getToolName , isToolStateCompleted , isToolStateError , isToolStateRunning , getDefaultToolAction } from "./tool-call/utils"
31+ import {
32+ ensureMarkdownContent ,
33+ getRelativePath ,
34+ getToolIcon ,
35+ getToolName ,
36+ isToolStateCompleted ,
37+ isToolStateError ,
38+ isToolStateRunning ,
39+ getDefaultToolAction ,
40+ readToolStatePayload ,
41+ } from "./tool-call/utils"
3142import { resolveTitleForTool } from "./tool-call/tool-title"
3243import { getLogger } from "../lib/logger"
3344
@@ -155,12 +166,33 @@ export default function ToolCall(props: ToolCallProps) {
155166 const prefExpanded = toolOutputDefaultExpanded ( )
156167 const toolName = toolCallMemo ( ) ?. tool || ""
157168 if ( toolName === "read" ) {
169+ const state = toolState ( )
170+ if ( state ?. status === "error" ) {
171+ return true
172+ }
158173 return false
159174 }
160175 return prefExpanded
161176 } )
162177
163178 const [ userExpanded , setUserExpanded ] = createSignal < boolean | null > ( null )
179+ const toolInputsVisibility = createMemo ( ( ) => preferences ( ) . toolInputsVisibility || "collapsed" )
180+ const [ toolInputVisibilityOverride , setToolInputVisibilityOverride ] = createSignal < "hidden" | "expanded" | null > ( null )
181+ const effectiveToolInputsVisibility = createMemo ( ( ) => toolInputVisibilityOverride ( ) ?? toolInputsVisibility ( ) )
182+ const isToolInputVisible = createMemo ( ( ) => effectiveToolInputsVisibility ( ) !== "hidden" )
183+ const inputDefaultExpanded = createMemo ( ( ) => effectiveToolInputsVisibility ( ) === "expanded" )
184+ const [ inputSectionOverride , setInputSectionOverride ] = createSignal < boolean | null > ( null )
185+ const [ outputSectionOverride , setOutputSectionOverride ] = createSignal < boolean | null > ( null )
186+ const inputSectionExpanded = ( ) => {
187+ const override = inputSectionOverride ( )
188+ if ( override !== null ) return override
189+ return inputDefaultExpanded ( )
190+ }
191+ const outputSectionExpanded = ( ) => {
192+ const override = outputSectionOverride ( )
193+ if ( override !== null ) return override
194+ return true
195+ }
164196
165197 const isPermissionActive = createMemo ( ( ) => {
166198 const pending = pendingPermission ( )
@@ -183,6 +215,35 @@ export default function ToolCall(props: ToolCallProps) {
183215 return defaultExpandedForTool ( )
184216 }
185217
218+ const toolInput = createMemo ( ( ) => {
219+ const state = toolState ( )
220+ return readToolStatePayload ( state ) . input
221+ } )
222+
223+ const hasToolInput = createMemo ( ( ) => {
224+ const input = toolInput ( )
225+ return input && Object . keys ( input ) . length > 0
226+ } )
227+
228+ const toolInputMarkdown = createMemo ( ( ) => {
229+ const input = toolInput ( )
230+ if ( ! input || Object . keys ( input ) . length === 0 ) return null
231+
232+ try {
233+ const yamlText = stringifyYaml ( input )
234+ return ensureMarkdownContent ( yamlText , "yaml" , true )
235+ } catch ( error ) {
236+ log . error ( "Failed to convert tool call input to YAML" , error )
237+ try {
238+ const jsonText = JSON . stringify ( input , null , 2 )
239+ return ensureMarkdownContent ( jsonText , "json" , true )
240+ } catch ( nestedError ) {
241+ log . error ( "Failed to stringify tool call input" , nestedError )
242+ return null
243+ }
244+ }
245+ } )
246+
186247 const permissionDetails = createMemo ( ( ) => pendingPermission ( ) ?. permission )
187248 const questionDetails = createMemo ( ( ) => pendingQuestion ( ) ?. request )
188249
@@ -548,6 +609,25 @@ export default function ToolCall(props: ToolCallProps) {
548609 } )
549610 }
550611
612+ createEffect ( ( ) => {
613+ // When global preference changes, reset per-tool-call overrides so palette changes apply.
614+ toolInputsVisibility ( )
615+ setToolInputVisibilityOverride ( null )
616+ setInputSectionOverride ( null )
617+ setOutputSectionOverride ( null )
618+ } )
619+
620+ const handleToggleInputVisibility = ( event : MouseEvent ) => {
621+ event . preventDefault ( )
622+ event . stopPropagation ( )
623+ if ( ! expanded ( ) ) {
624+ toggle ( )
625+ }
626+
627+ const currentlyVisible = isToolInputVisible ( )
628+ setToolInputVisibilityOverride ( currentlyVisible ? "hidden" : "expanded" )
629+ }
630+
551631 const renderer = createMemo ( ( ) => resolveToolRenderer ( toolName ( ) ) )
552632
553633 const { renderAnsiContent } = createAnsiContentRenderer ( {
@@ -789,6 +869,23 @@ export default function ToolCall(props: ToolCallProps) {
789869 </ span >
790870 </ button >
791871
872+ < Show when = { hasToolInput ( ) } >
873+ < button
874+ type = "button"
875+ class = "tool-call-header-input"
876+ onClick = { handleToggleInputVisibility }
877+ aria-pressed = { isToolInputVisible ( ) }
878+ aria-label = {
879+ isToolInputVisible ( )
880+ ? t ( "toolCall.header.hideInputAriaLabel" )
881+ : t ( "toolCall.header.showInputAriaLabel" )
882+ }
883+ title = { isToolInputVisible ( ) ? t ( "toolCall.header.hideInputTitle" ) : t ( "toolCall.header.showInputTitle" ) }
884+ >
885+ < ArrowRightSquare class = "w-3.5 h-3.5" />
886+ </ button >
887+ </ Show >
888+
792889 < button
793890 type = "button"
794891 class = "tool-call-header-copy"
@@ -806,19 +903,79 @@ export default function ToolCall(props: ToolCallProps) {
806903
807904 { expanded ( ) && (
808905 < div class = "tool-call-details" >
809- { renderToolBody ( ) }
810-
811- { renderError ( ) }
812-
813- { renderPermissionBlock ( ) }
814- { renderQuestionBlock ( ) }
815-
816- < Show when = { status ( ) === "pending" && ! pendingPermission ( ) } >
817- < div class = "tool-call-pending-message" >
818- < span class = "spinner-small" > </ span >
819- < span > { t ( "toolCall.pending.waitingToRun" ) } </ span >
906+ < Show
907+ when = { isToolInputVisible ( ) && hasToolInput ( ) }
908+ fallback = {
909+ < >
910+ { renderToolBody ( ) }
911+ { renderError ( ) }
912+
913+ < Show when = { status ( ) === "pending" && ! pendingPermission ( ) } >
914+ < div class = "tool-call-pending-message" >
915+ < span class = "spinner-small" > </ span >
916+ < span > { t ( "toolCall.pending.waitingToRun" ) } </ span >
917+ </ div >
918+ </ Show >
919+ </ >
920+ }
921+ >
922+ < div class = "tool-call-io-sections" >
923+ < div class = "tool-call-io-section" >
924+ < button
925+ type = "button"
926+ class = "tool-call-io-toggle"
927+ aria-expanded = { inputSectionExpanded ( ) }
928+ onClick = { ( ) => setInputSectionOverride ( ( prev ) => {
929+ const current = prev === null ? inputSectionExpanded ( ) : prev
930+ return ! current
931+ } ) }
932+ >
933+ < span class = "tool-call-io-title" > { t ( "toolCall.io.input" ) } </ span >
934+ </ button >
935+
936+ < Show when = { inputSectionExpanded ( ) } >
937+ < div class = "tool-call-io-body" >
938+ { ( ( ) => {
939+ const content = toolInputMarkdown ( )
940+ if ( ! content ) return null
941+ return renderMarkdownContent ( { content, cacheKey : "input" } )
942+ } ) ( ) }
943+ </ div >
944+ </ Show >
945+ </ div >
946+
947+ < div class = "tool-call-io-section" >
948+ < button
949+ type = "button"
950+ class = "tool-call-io-toggle"
951+ aria-expanded = { outputSectionExpanded ( ) }
952+ onClick = { ( ) => setOutputSectionOverride ( ( prev ) => {
953+ const current = prev === null ? outputSectionExpanded ( ) : prev
954+ return ! current
955+ } ) }
956+ >
957+ < span class = "tool-call-io-title" > { t ( "toolCall.io.output" ) } </ span >
958+ </ button >
959+
960+ < Show when = { outputSectionExpanded ( ) } >
961+ < div class = "tool-call-io-body" >
962+ { renderToolBody ( ) }
963+ { renderError ( ) }
964+
965+ < Show when = { status ( ) === "pending" && ! pendingPermission ( ) } >
966+ < div class = "tool-call-pending-message" >
967+ < span class = "spinner-small" > </ span >
968+ < span > { t ( "toolCall.pending.waitingToRun" ) } </ span >
969+ </ div >
970+ </ Show >
971+ </ div >
972+ </ Show >
973+ </ div >
820974 </ div >
821975 </ Show >
976+
977+ { renderPermissionBlock ( ) }
978+ { renderQuestionBlock ( ) }
822979 </ div >
823980 ) }
824981
0 commit comments