11import { useMemo , useState } from "react" ;
22import { FunctionCall } from "@/types" ;
33import { Card , CardHeader , CardTitle , CardContent } from "@/components/ui/card" ;
4- import { convertToUserFriendlyName } from "@/lib/utils" ;
4+ import { convertToUserFriendlyName , isAgentToolName } from "@/lib/utils" ;
55import { ChevronDown , ChevronUp , MessageSquare , Loader2 , AlertCircle , CheckCircle } from "lucide-react" ;
66import KagentLogo from "../kagent-logo" ;
7+ import ToolDisplay , { ToolCallStatus } from "@/components/ToolDisplay" ;
78
89export type AgentCallStatus = "requested" | "executing" | "completed" ;
910
11+ // Constants
12+ const MAX_NESTING_DEPTH = 10 ;
13+ const NESTING_INDENT_REM = 1.5 ;
14+
15+ interface NestedToolCall {
16+ id : string ;
17+ call : FunctionCall ;
18+ result ?: {
19+ content : string ;
20+ is_error ?: boolean ;
21+ } ;
22+ status : ToolCallStatus ;
23+ nestedCalls ?: NestedToolCall [ ] ;
24+ }
25+
1026interface AgentCallDisplayProps {
1127 call : FunctionCall ;
1228 result ?: {
@@ -15,14 +31,31 @@ interface AgentCallDisplayProps {
1531 } ;
1632 status ?: AgentCallStatus ;
1733 isError ?: boolean ;
34+ nestedCalls ?: NestedToolCall [ ] ; // Support for nested agent/tool calls
35+ depth ?: number ; // Track nesting depth for visual indentation
1836}
1937
20- const AgentCallDisplay = ( { call, result, status = "requested" , isError = false } : AgentCallDisplayProps ) => {
38+ const AgentCallDisplay = ( { call, result, status = "requested" , isError = false , nestedCalls = [ ] , depth = 0 } : AgentCallDisplayProps ) => {
2139 const [ areInputsExpanded , setAreInputsExpanded ] = useState ( false ) ;
2240 const [ areResultsExpanded , setAreResultsExpanded ] = useState ( false ) ;
41+ const [ areNestedCallsExpanded , setAreNestedCallsExpanded ] = useState ( true ) ; // Expanded by default for better visibility
2342
2443 const agentDisplay = useMemo ( ( ) => convertToUserFriendlyName ( call . name ) , [ call . name ] ) ;
2544 const hasResult = result !== undefined ;
45+ const hasNestedCalls = nestedCalls && nestedCalls . length > 0 ;
46+
47+ // Protection against infinite recursion
48+ if ( depth > MAX_NESTING_DEPTH ) {
49+ console . warn ( `Maximum nesting depth (${ MAX_NESTING_DEPTH } ) reached for agent call:` , call . name ) ;
50+ return (
51+ < div className = "p-2 text-xs text-muted-foreground border border-yellow-500 rounded" >
52+ ⚠️ Maximum nesting depth reached
53+ </ div >
54+ ) ;
55+ }
56+
57+ // Calculate left margin based on nesting depth
58+ const marginLeft = depth > 0 ? `${ depth * NESTING_INDENT_REM } rem` : '0' ;
2659
2760 const getStatusDisplay = ( ) => {
2861 if ( isError && status === "executing" ) {
@@ -69,59 +102,100 @@ const AgentCallDisplay = ({ call, result, status = "requested", isError = false
69102 } ;
70103
71104 return (
72- < Card className = { `w-full mx-auto my-1 min-w-full ${ isError ? 'border-red-300' : '' } ` } >
73- < CardHeader className = "flex flex-row items-center justify-between space-y-0 pb-2" >
74- < CardTitle className = "text-xs flex space-x-5" >
75- < div className = "flex items-center font-medium" >
76- < KagentLogo className = "w-4 h-4 mr-2" />
77- { agentDisplay }
78- </ div >
79- < div className = "font-light" > { call . id } </ div >
80- </ CardTitle >
81- < div className = "flex justify-center items-center text-xs" >
82- { getStatusDisplay ( ) }
83- </ div >
84- </ CardHeader >
85- < CardContent >
86- < div className = "space-y-2 mt-2" >
87- < button className = "text-xs flex items-center gap-2" onClick = { ( ) => setAreInputsExpanded ( ! areInputsExpanded ) } >
88- < MessageSquare className = "w-4 h-4" />
89- < span > Input</ span >
90- { areInputsExpanded ? < ChevronUp className = "w-4 h-4 ml-1" /> : < ChevronDown className = "w-4 h-4 ml-1" /> }
91- </ button >
92- { areInputsExpanded && (
93- < div className = "mt-2 bg-muted/50 p-3 rounded" >
94- < pre className = "text-sm whitespace-pre-wrap break-words" > { JSON . stringify ( call . args , null , 2 ) } </ pre >
105+ < div style = { { marginLeft } } >
106+ < Card className = { `w-full mx-auto my-1 min-w-full ${ isError ? 'border-red-300' : '' } ${ depth > 0 ? 'border-l-4 border-l-blue-400' : '' } ` } >
107+ < CardHeader className = "flex flex-row items-center justify-between space-y-0 pb-2" >
108+ < CardTitle className = "text-xs flex space-x-5" >
109+ < div className = "flex items-center font-medium" >
110+ < KagentLogo className = "w-4 h-4 mr-2" />
111+ { agentDisplay }
112+ { depth > 0 && < span className = "ml-2 text-xs text-muted-foreground" > (nested level { depth } )</ span > }
95113 </ div >
96- ) }
97- </ div >
114+ < div className = "font-light" > { call . id } </ div >
115+ </ CardTitle >
116+ < div className = "flex justify-center items-center text-xs" >
117+ { getStatusDisplay ( ) }
118+ </ div >
119+ </ CardHeader >
120+ < CardContent >
121+ < div className = "space-y-2 mt-2" >
122+ < button className = "text-xs flex items-center gap-2" onClick = { ( ) => setAreInputsExpanded ( ! areInputsExpanded ) } >
123+ < MessageSquare className = "w-4 h-4" />
124+ < span > Input</ span >
125+ { areInputsExpanded ? < ChevronUp className = "w-4 h-4 ml-1" /> : < ChevronDown className = "w-4 h-4 ml-1" /> }
126+ </ button >
127+ { areInputsExpanded && (
128+ < div className = "mt-2 bg-muted/50 p-3 rounded" >
129+ < pre className = "text-sm whitespace-pre-wrap break-words" > { JSON . stringify ( call . args , null , 2 ) } </ pre >
130+ </ div >
131+ ) }
132+ </ div >
98133
99- < div className = "mt-4 w-full" >
100- { status === "executing" && ! hasResult && (
101- < div className = "flex items-center gap-2 py-2" >
102- < Loader2 className = "h-4 w-4 animate-spin" />
103- < span className = "text-sm" > { agentDisplay } is responding...</ span >
104- </ div >
105- ) }
106- { hasResult && result ?. content && (
107- < div className = "space-y-2" >
108- < button className = "text-xs flex items-center gap-2" onClick = { ( ) => setAreResultsExpanded ( ! areResultsExpanded ) } >
109- < MessageSquare className = "w-4 h-4" />
110- < span > Output</ span >
111- { areResultsExpanded ? < ChevronUp className = "w-4 h-4 ml-1" /> : < ChevronDown className = "w-4 h-4 ml-1" /> }
134+ < div className = "mt-4 w-full" >
135+ { status === "executing" && ! hasResult && (
136+ < div className = "flex items-center gap-2 py-2" >
137+ < Loader2 className = "h-4 w-4 animate-spin" />
138+ < span className = "text-sm" > { agentDisplay } is responding...</ span >
139+ </ div >
140+ ) }
141+ { hasResult && result ?. content && (
142+ < div className = "space-y-2" >
143+ < button className = "text-xs flex items-center gap-2" onClick = { ( ) => setAreResultsExpanded ( ! areResultsExpanded ) } >
144+ < MessageSquare className = "w-4 h-4" />
145+ < span > Output</ span >
146+ { areResultsExpanded ? < ChevronUp className = "w-4 h-4 ml-1" /> : < ChevronDown className = "w-4 h-4 ml-1" /> }
147+ </ button >
148+ { areResultsExpanded && (
149+ < div className = { `mt-2 ${ isError ? 'bg-red-50 dark:bg-red-950/10' : 'bg-muted/50' } p-3 rounded` } >
150+ < pre className = { `text-sm whitespace-pre-wrap break-words ${ isError ? 'text-red-600 dark:text-red-400' : '' } ` } >
151+ { result ?. content }
152+ </ pre >
153+ </ div >
154+ ) }
155+ </ div >
156+ ) }
157+ </ div >
158+
159+ { /* Nested agent/tool calls section */ }
160+ { hasNestedCalls && (
161+ < div className = "mt-4 border-t pt-4" >
162+ < button
163+ className = "text-xs flex items-center gap-2 font-semibold mb-2"
164+ onClick = { ( ) => setAreNestedCallsExpanded ( ! areNestedCallsExpanded ) }
165+ >
166+ < span > Delegated Calls ({ nestedCalls . length } )</ span >
167+ { areNestedCallsExpanded ? < ChevronUp className = "w-4 h-4 ml-1" /> : < ChevronDown className = "w-4 h-4 ml-1" /> }
112168 </ button >
113- { areResultsExpanded && (
114- < div className = { `mt-2 ${ isError ? 'bg-red-50 dark:bg-red-950/10' : 'bg-muted/50' } p-3 rounded` } >
115- < pre className = { `text-sm whitespace-pre-wrap break-words ${ isError ? 'text-red-600 dark:text-red-400' : '' } ` } >
116- { result ?. content }
117- </ pre >
169+ { areNestedCallsExpanded && (
170+ < div className = "space-y-2 mt-2" >
171+ { nestedCalls . map ( ( nestedCall ) => (
172+ isAgentToolName ( nestedCall . call . name ) ? (
173+ < AgentCallDisplay
174+ key = { nestedCall . id }
175+ call = { nestedCall . call }
176+ result = { nestedCall . result }
177+ status = { nestedCall . status }
178+ isError = { nestedCall . result ?. is_error }
179+ nestedCalls = { nestedCall . nestedCalls }
180+ depth = { depth + 1 }
181+ />
182+ ) : (
183+ < ToolDisplay
184+ key = { nestedCall . id }
185+ call = { nestedCall . call }
186+ result = { nestedCall . result }
187+ status = { nestedCall . status }
188+ isError = { nestedCall . result ?. is_error }
189+ />
190+ )
191+ ) ) }
118192 </ div >
119193 ) }
120194 </ div >
121195 ) }
122- </ div >
123- </ CardContent >
124- </ Card >
196+ </ CardContent >
197+ </ Card >
198+ </ div >
125199 ) ;
126200} ;
127201
0 commit comments