@@ -12,10 +12,17 @@ pub trait ToolCaller: Send + Sync {
1212 fn tools ( & self ) -> Vec < ToolInfo > ;
1313}
1414
15+ #[ derive( Debug , Clone ) ]
16+ pub struct ImageData {
17+ pub data : String ,
18+ pub mime_type : String ,
19+ }
20+
1521#[ derive( Debug , Clone , Default ) ]
1622pub struct ExecutionResult {
1723 pub value : JsonValue ,
1824 pub logs : Vec < String > ,
25+ pub images : Vec < ImageData > ,
1926 pub is_error : bool ,
2027 pub error_message : Option < String > ,
2128}
@@ -67,6 +74,9 @@ impl JsRuntime {
6774
6875 let logs: Arc < std:: sync:: Mutex < Vec < String > > > = Arc :: new ( std:: sync:: Mutex :: new ( Vec :: new ( ) ) ) ;
6976 let logs_clone = logs. clone ( ) ;
77+ let images: Arc < std:: sync:: Mutex < Vec < ImageData > > > =
78+ Arc :: new ( std:: sync:: Mutex :: new ( Vec :: new ( ) ) ) ;
79+ let images_clone = images. clone ( ) ;
7080
7181 let context = JsContext :: full ( & self . runtime ) ?;
7282
@@ -108,6 +118,7 @@ impl JsRuntime {
108118 for tool_name in & tool_names {
109119 let name = tool_name. clone ( ) ;
110120 let caller_clone = caller. clone ( ) ;
121+ let images_for_closure = images_clone. clone ( ) ;
111122
112123 let func = Function :: new ( ctx. clone ( ) , move |args : String | {
113124 let tool_name = name. clone ( ) ;
@@ -117,7 +128,10 @@ impl JsRuntime {
117128 let result = caller. call_tool ( & tool_name, args_value) ;
118129
119130 match result {
120- Ok ( call_result) => format_call_result ( & call_result) ,
131+ Ok ( call_result) => {
132+ collect_images ( & call_result, & images_for_closure) ;
133+ format_call_result ( & call_result)
134+ }
121135 Err ( e) => serde_json:: json!( { "error" : e. to_string( ) } ) . to_string ( ) ,
122136 }
123137 } ) ?;
@@ -183,36 +197,66 @@ impl JsRuntime {
183197 } )
184198 . map ( |( value, error) | {
185199 let captured_logs = logs. lock ( ) . map ( |l| l. clone ( ) ) . unwrap_or_default ( ) ;
200+ let captured_images = images. lock ( ) . map ( |i| i. clone ( ) ) . unwrap_or_default ( ) ;
186201 ExecutionResult {
187202 value,
188203 logs : captured_logs,
204+ images : captured_images,
189205 is_error : error. is_some ( ) ,
190206 error_message : error,
191207 }
192208 } )
193209 }
194210}
195211
212+ fn collect_images ( result : & CallToolResult , images : & Arc < std:: sync:: Mutex < Vec < ImageData > > > ) {
213+ for content in & result. content {
214+ if let crate :: CallToolResultContent :: Image { data, mime_type } = content {
215+ if let Ok ( mut collected) = images. lock ( ) {
216+ collected. push ( ImageData {
217+ data : data. clone ( ) ,
218+ mime_type : mime_type. clone ( ) ,
219+ } ) ;
220+ }
221+ }
222+ }
223+ }
224+
196225fn format_call_result ( result : & CallToolResult ) -> String {
197226 // Prefer structured_content if available
198227 if let Some ( structured) = & result. structured_content {
199228 return serde_json:: to_string ( structured) . unwrap_or_else ( |_| "null" . to_string ( ) ) ;
200229 }
201230
202- // Otherwise, extract text content
231+ // Otherwise, convert all content blocks to JSON-friendly values
203232 let contents: Vec < JsonValue > = result
204233 . content
205234 . iter ( )
206- . filter_map ( |c| {
207- if let crate :: CallToolResultContent :: Text { text } = c {
208- Some ( JsonValue :: String ( text. clone ( ) ) )
209- } else {
210- None
211- }
235+ . map ( |content| match content {
236+ crate :: CallToolResultContent :: Text { text } => JsonValue :: String ( text. clone ( ) ) ,
237+ crate :: CallToolResultContent :: Image { data, mime_type } => serde_json:: json!( {
238+ "type" : "image" ,
239+ "data" : data,
240+ "mimeType" : mime_type,
241+ } ) ,
242+ crate :: CallToolResultContent :: ResourceLink {
243+ uri,
244+ name,
245+ description,
246+ mime_type,
247+ annotations,
248+ } => serde_json:: json!( {
249+ "type" : "resource_link" ,
250+ "uri" : uri,
251+ "name" : name,
252+ "description" : description,
253+ "mimeType" : mime_type,
254+ "annotations" : annotations,
255+ } ) ,
212256 } )
213257 . collect ( ) ;
214258
215- // If single text content, try to parse as JSON or return as string
259+ // If there's a single content block, unwrap it for convenience.
216260 if contents. len ( ) == 1 {
217261 if let Some ( s) = contents[ 0 ] . as_str ( ) {
218262 // Try to parse as JSON first
@@ -221,6 +265,7 @@ fn format_call_result(result: &CallToolResult) -> String {
221265 }
222266 return s. to_string ( ) ;
223267 }
268+ return serde_json:: to_string ( & contents[ 0 ] ) . unwrap_or_else ( |_| "null" . to_string ( ) ) ;
224269 }
225270
226271 serde_json:: to_string ( & contents) . unwrap_or_else ( |_| "[]" . to_string ( ) )
@@ -339,6 +384,19 @@ mod tests {
339384 & serde_json:: json!( { "message" : format!( "Hello, {}!" , name) } ) ,
340385 ) )
341386 }
387+ "render" => Ok ( CallToolResult {
388+ content : vec ! [
389+ crate :: CallToolResultContent :: Image {
390+ data: "AA==" . to_string( ) ,
391+ mime_type: "image/png" . to_string( ) ,
392+ } ,
393+ crate :: CallToolResultContent :: Text {
394+ text: "Rendered" . to_string( ) ,
395+ } ,
396+ ] ,
397+ structured_content : None ,
398+ is_error : false ,
399+ } ) ,
342400 _ => Err ( anyhow:: anyhow!( "Unknown tool: {}" , name) ) ,
343401 }
344402 }
@@ -375,6 +433,15 @@ mod tests {
375433 } ) ,
376434 output_schema: None ,
377435 } ,
436+ ToolInfo {
437+ name: "render" ,
438+ description: "Render a PNG image" ,
439+ input_schema: serde_json:: json!( {
440+ "type" : "object" ,
441+ "properties" : { }
442+ } ) ,
443+ output_schema: None ,
444+ } ,
378445 ] ,
379446 } ) ;
380447
@@ -413,6 +480,32 @@ mod tests {
413480 assert_eq ! ( result. value[ "greeting" ] , "Hello, Alice!" ) ;
414481 }
415482
483+ #[ test]
484+ fn test_image_content_preserved ( ) {
485+ let caller = Arc :: new ( MockToolCaller {
486+ tools : vec ! [ ToolInfo {
487+ name: "render" ,
488+ description: "Render a PNG image" ,
489+ input_schema: serde_json:: json!( {
490+ "type" : "object" ,
491+ "properties" : { }
492+ } ) ,
493+ output_schema: None ,
494+ } ] ,
495+ } ) ;
496+
497+ let runtime = JsRuntime :: new ( ) . unwrap ( ) ;
498+ let result = runtime
499+ . execute_with_tools ( "tools.render({})" , caller)
500+ . unwrap ( ) ;
501+
502+ assert ! ( !result. is_error, "Error: {:?}" , result. error_message) ;
503+ assert_eq ! ( result. images. len( ) , 1 ) ;
504+ assert_eq ! ( result. images[ 0 ] . mime_type, "image/png" ) ;
505+ assert_eq ! ( result. value[ 0 ] [ "type" ] , "image" ) ;
506+ assert_eq ! ( result. value[ 0 ] [ "mimeType" ] , "image/png" ) ;
507+ }
508+
416509 #[ test]
417510 fn test_console_log_capture ( ) {
418511 let caller = Arc :: new ( MockToolCaller { tools : vec ! [ ] } ) ;
0 commit comments