@@ -3,11 +3,25 @@ import {
33 GitDiffIcon as GitDiff ,
44 TerminalWindowIcon as TerminalSquare ,
55} from "@/ui/icons" ;
6- import { getAcpDiffOutputs , openAcpDiffOutput } from "@/features/ai/lib/acp-diff-output" ;
6+ import {
7+ createAcpDiffViewNode ,
8+ getAcpDiffOutputs ,
9+ openAcpDiffOutput ,
10+ stripAcpDiffOutputs ,
11+ } from "@/features/ai/lib/acp-diff-output" ;
12+ import {
13+ getStructuredToolViews ,
14+ isStructuredToolViewEnvelope ,
15+ stripStructuredToolViews ,
16+ } from "@/features/ai/lib/structured-tool-view" ;
717import {
818 getAcpTerminalOutputs ,
919 openAcpTerminalOutput ,
1020} from "@/features/ai/lib/acp-terminal-output" ;
21+ import {
22+ createAcpToolLocationTree ,
23+ OPEN_TOOL_LOCATION_COMMAND ,
24+ } from "@/features/ai/lib/acp-tool-location-tree" ;
1125import type { ToolCall } from "@/features/ai/types/ai-chat.types" ;
1226import type {
1327 AcpToolCallLocation ,
@@ -19,6 +33,8 @@ import { readFileContent } from "@/features/file-system/controllers/file-operati
1933import { getFileDiff } from "@/features/git/api/git-diff-api" ;
2034import { useProjectStore } from "@/features/window/stores/project.store" ;
2135import { Button } from "@/ui/button" ;
36+ import { GenerativeUIRenderer } from "@/extensions/ui/components/generative-ui-renderer" ;
37+ import { ExtensionViewRenderer } from "@/extensions/ui/components/extension-view-renderer" ;
2238import { getBaseName , joinPath } from "@/utils/path-helpers" ;
2339import { ChatActivityLine } from "../chat/chat-activity-line" ;
2440
@@ -174,12 +190,6 @@ function formatDiffText(item: Record<string, unknown>): string {
174190 . join ( "\n" ) ;
175191}
176192
177- function formatAcpDiffText ( item : ReturnType < typeof getAcpDiffOutputs > [ number ] ) : string {
178- return [ `diff: ${ item . path } ` , "--- before" , item . oldText , "+++ after" , item . newText ]
179- . filter ( ( line ) => line . length > 0 )
180- . join ( "\n" ) ;
181- }
182-
183193function getOutputSummary ( output : unknown ) : string | null {
184194 const diffItems = getDiffItems ( output ) ;
185195 if ( diffItems . length > 0 ) {
@@ -192,14 +202,18 @@ function getOutputSummary(output: unknown): string | null {
192202 return terminalItems . length === 1 ? "terminal output" : `${ terminalItems . length } terminals` ;
193203 }
194204
205+ if ( getStructuredToolViews ( output ) . length > 0 ) return "interactive result" ;
206+
195207 return null ;
196208}
197209
198210function getOutputText ( output : unknown ) : string {
211+ if ( isStructuredToolViewEnvelope ( output ) ) return "" ;
199212 if ( ! Array . isArray ( output ) ) return formatValue ( output ) ;
200213
201214 return output
202215 . map ( ( item ) => {
216+ if ( isStructuredToolViewEnvelope ( item ) ) return "" ;
203217 if ( ! isRecord ( item ) ) return formatValue ( item ) ;
204218 if ( item . type === "content" ) return getContentText ( item ) ;
205219 if ( item . type === "diff" ) return formatDiffText ( item ) ;
@@ -251,7 +265,7 @@ export function ToolCallGroupDisplay({
251265 const detail = getLatestToolSummary ( latestToolCall , isStreaming ) ;
252266
253267 return (
254- < ChatActivityLine title = { title } detail = { detail } >
268+ < ChatActivityLine title = { title } detail = { detail } detailsVariant = "content" >
255269 < div className = "space-y-1" >
256270 { toolCalls . map ( ( toolCall , toolIndex ) => (
257271 < ToolCallDisplay
@@ -283,17 +297,26 @@ function ToolCallDisplay({
283297} : ToolCallDisplayProps ) {
284298 const state = getStatus ( isStreaming , error , protocolStatus ) ;
285299 const detail = getToolCallDetail ( toolName , input , output , state , protocolStatus ) ;
300+ const structuredViews = getStructuredToolViews ( output ) ;
301+ const displayOutput = stripStructuredToolViews ( output ) ;
302+ const hasStructuredViews = structuredViews . length > 0 ;
286303 const hasDetails =
287304 Boolean ( input ) ||
288- Boolean ( output ) ||
305+ Boolean ( displayOutput ) ||
306+ hasStructuredViews ||
289307 Boolean ( error ) ||
290308 Boolean ( kind && kind !== "other" ) ||
291309 Boolean ( locations ?. length ) ;
292- const diffItems = getDiffItems ( output ) ;
310+ const diffItems = getDiffItems ( displayOutput ) ;
293311 const hasDiffOutput = diffItems . length > 0 ;
294- const terminalItems = getTerminalItems ( output ) ;
312+ const nonDiffOutput = stripAcpDiffOutputs ( displayOutput ) ;
313+ const diffViews = diffItems . map ( ( item ) =>
314+ createAcpDiffViewNode ( item , useProjectStore . getState ( ) . rootFolderPath ) ,
315+ ) ;
316+ const terminalItems = getTerminalItems ( displayOutput ) ;
295317 const hasTerminalOutput = terminalItems . length > 0 ;
296318 const toolPath = resolveToolPath ( locations , input ) ;
319+ const locationTree = locations ? createAcpToolLocationTree ( locations ) : undefined ;
297320 const hasActions = Boolean ( toolPath || hasTerminalOutput ) ;
298321 const actionButtons = hasActions ? (
299322 < span className = "flex items-center gap-1" >
@@ -341,27 +364,61 @@ function ToolCallDisplay({
341364 ) : null }
342365 </ span >
343366 ) : null ;
367+ const detailText = [
368+ kind && kind !== "other" ? `kind: ${ kind } \n` : "" ,
369+ locations ?. length && ! locationTree
370+ ? `locations:\n${ locations
371+ . map ( ( location ) => ` ${ location . path } ${ location . line ? `:${ location . line } ` : "" } ` )
372+ . join ( "\n" ) } \n`
373+ : "" ,
374+ input ? `input:\n${ formatValue ( input ) } \n` : "" ,
375+ nonDiffOutput ? `output:\n${ getOutputText ( nonDiffOutput ) } \n` : "" ,
376+ error ? `error:\n${ error } ` : "" ,
377+ ] . join ( "" ) ;
344378
345379 return (
346- < ChatActivityLine title = { toolName } detail = { detail } state = { state } actions = { actionButtons } >
380+ < ChatActivityLine
381+ title = { toolName }
382+ detail = { detail }
383+ state = { state }
384+ actions = { actionButtons }
385+ detailsVariant = { hasStructuredViews || hasDiffOutput || locationTree ? "content" : "text" }
386+ >
347387 { hasDetails ? (
348- < >
349- { kind && kind !== "other" ? `kind: ${ kind } \n` : "" }
350- { locations ?. length
351- ? `locations:\n${ locations
352- . map ( ( location ) => ` ${ location . path } ${ location . line ? `:${ location . line } ` : "" } ` )
353- . join ( "\n" ) } \n`
354- : "" }
355- { input ? `input:\n${ formatValue ( input ) } \n` : "" }
356- { output
357- ? `output:\n${
358- hasDiffOutput
359- ? diffItems . map ( formatAcpDiffText ) . join ( "\n\n" )
360- : getOutputText ( output )
361- } \n`
362- : "" }
363- { error ? `error:\n${ error } ` : "" }
364- </ >
388+ hasStructuredViews || hasDiffOutput || locationTree ? (
389+ < div className = "flex min-w-0 flex-col gap-2" >
390+ { detailText ? (
391+ < pre className = "max-h-64 overflow-auto whitespace-pre-wrap font-mono ui-text-sm text-subtle-foreground/55" >
392+ { detailText }
393+ </ pre >
394+ ) : null }
395+ { diffViews . map ( ( view , index ) => (
396+ < ExtensionViewRenderer
397+ key = { `${ view . filePath } -diff-${ index } ` }
398+ node = { view }
399+ execute = { ( ) => undefined }
400+ surface = "embedded"
401+ />
402+ ) ) }
403+ { locationTree ? (
404+ < ExtensionViewRenderer
405+ node = { locationTree }
406+ execute = { ( action ) => {
407+ const path = action . args ?. [ 0 ] ;
408+ if ( action . command === OPEN_TOOL_LOCATION_COMMAND && typeof path === "string" ) {
409+ return openToolPath ( path ) ;
410+ }
411+ } }
412+ surface = "embedded"
413+ />
414+ ) : null }
415+ { structuredViews . map ( ( view , index ) => (
416+ < GenerativeUIRenderer key = { `${ toolName } -ui-${ index } ` } component = { view } />
417+ ) ) }
418+ </ div >
419+ ) : (
420+ detailText
421+ )
365422 ) : null }
366423 </ ChatActivityLine >
367424 ) ;
0 commit comments