@@ -15,6 +15,56 @@ interface ToolResultsProps {
1515 isPollingTask ?: boolean ;
1616}
1717
18+ const checkContentCompatibility = (
19+ structuredContent : unknown ,
20+ unstructuredContent : Array < {
21+ type : string ;
22+ text ?: string ;
23+ [ key : string ] : unknown ;
24+ } > ,
25+ ) : { isCompatible : boolean ; message : string } => {
26+ // Look for at least one text content block that matches the structured content
27+ const textBlocks = unstructuredContent . filter (
28+ ( block ) => block . type === "text" ,
29+ ) ;
30+
31+ if ( textBlocks . length === 0 ) {
32+ return {
33+ isCompatible : false ,
34+ message : "No text blocks to match structured content" ,
35+ } ;
36+ }
37+
38+ // Check if any text block contains JSON that matches the structured content
39+ for ( const textBlock of textBlocks ) {
40+ const textContent = textBlock . text ;
41+ if ( ! textContent ) {
42+ continue ;
43+ }
44+
45+ try {
46+ const parsedContent = JSON . parse ( textContent ) ;
47+ const isEqual =
48+ JSON . stringify ( parsedContent ) === JSON . stringify ( structuredContent ) ;
49+
50+ if ( isEqual ) {
51+ return {
52+ isCompatible : true ,
53+ message : `Structured content matches text block${ textBlocks . length > 1 ? " (multiple blocks)" : "" } ${ unstructuredContent . length > textBlocks . length ? " + other content" : "" } ` ,
54+ } ;
55+ }
56+ } catch {
57+ // Continue to next text block if this one doesn't parse as JSON
58+ continue ;
59+ }
60+ }
61+
62+ return {
63+ isCompatible : false ,
64+ message : "No text block matches structured content" ,
65+ } ;
66+ } ;
67+
1868const ToolResults = ( {
1969 toolResult,
2070 selectedTool,
@@ -73,6 +123,19 @@ const ToolResults = ({
73123 }
74124 }
75125
126+ let compatibilityResult = null ;
127+ if (
128+ structuredResult . structuredContent &&
129+ structuredResult . content . length > 0 &&
130+ selectedTool &&
131+ hasOutputSchema ( selectedTool . name )
132+ ) {
133+ compatibilityResult = checkContentCompatibility (
134+ structuredResult . structuredContent ,
135+ structuredResult . content ,
136+ ) ;
137+ }
138+
76139 return (
77140 < >
78141 < h4 className = "font-semibold mb-2" >
@@ -125,48 +188,66 @@ const ToolResults = ({
125188 </ div >
126189 </ div >
127190 ) }
128- { structuredResult . content . length > 0 &&
129- ! structuredResult . structuredContent && (
130- < div className = "mb-4" >
131- { structuredResult . content . map ( ( item , index ) => (
132- < div key = { index } className = "mb-2" >
133- { item . type === "text" && (
134- < JsonView data = { item . text } isError = { isError } />
135- ) }
136- { item . type === "image" && (
137- < img
138- src = { `data:${ item . mimeType } ;base64,${ item . data } ` }
139- alt = "Tool result image"
140- className = "max-w-full h-auto"
141- />
142- ) }
143- { item . type === "resource" &&
144- ( item . resource ?. mimeType ?. startsWith ( "audio/" ) &&
145- "blob" in item . resource ? (
146- < audio
147- controls
148- src = { `data:${ item . resource . mimeType } ;base64,${ item . resource . blob } ` }
149- className = "w-full"
150- >
151- < p > Your browser does not support audio playback</ p >
152- </ audio >
153- ) : (
154- < JsonView data = { item . resource } />
155- ) ) }
156- { item . type === "resource_link" && (
157- < ResourceLinkView
158- uri = { item . uri }
159- name = { item . name }
160- description = { item . description }
161- mimeType = { item . mimeType }
162- resourceContent = { resourceContent [ item . uri ] || "" }
163- onReadResource = { onReadResource }
164- />
165- ) }
166- </ div >
167- ) ) }
168- </ div >
169- ) }
191+ { structuredResult . content . length > 0 && (
192+ < div className = "mb-4" >
193+ { structuredResult . structuredContent && (
194+ < >
195+ < h5 className = "font-semibold mb-2 text-sm" >
196+ Unstructured Content:
197+ </ h5 >
198+ { compatibilityResult && (
199+ < div
200+ className = { `mb-2 p-2 rounded text-sm ${
201+ compatibilityResult . isCompatible
202+ ? "bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"
203+ : "bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200"
204+ } `}
205+ >
206+ { compatibilityResult . isCompatible ? "✓" : "⚠" } { " " }
207+ { compatibilityResult . message }
208+ </ div >
209+ ) }
210+ </ >
211+ ) }
212+ { structuredResult . content . map ( ( item , index ) => (
213+ < div key = { index } className = "mb-2" >
214+ { item . type === "text" && (
215+ < JsonView data = { item . text } isError = { isError } />
216+ ) }
217+ { item . type === "image" && (
218+ < img
219+ src = { `data:${ item . mimeType } ;base64,${ item . data } ` }
220+ alt = "Tool result image"
221+ className = "max-w-full h-auto"
222+ />
223+ ) }
224+ { item . type === "resource" &&
225+ ( item . resource ?. mimeType ?. startsWith ( "audio/" ) &&
226+ "blob" in item . resource ? (
227+ < audio
228+ controls
229+ src = { `data:${ item . resource . mimeType } ;base64,${ item . resource . blob } ` }
230+ className = "w-full"
231+ >
232+ < p > Your browser does not support audio playback</ p >
233+ </ audio >
234+ ) : (
235+ < JsonView data = { item . resource } />
236+ ) ) }
237+ { item . type === "resource_link" && (
238+ < ResourceLinkView
239+ uri = { item . uri }
240+ name = { item . name }
241+ description = { item . description }
242+ mimeType = { item . mimeType }
243+ resourceContent = { resourceContent [ item . uri ] || "" }
244+ onReadResource = { onReadResource }
245+ />
246+ ) }
247+ </ div >
248+ ) ) }
249+ </ div >
250+ ) }
170251 </ >
171252 ) ;
172253 } else if ( "toolResult" in toolResult ) {
0 commit comments