@@ -41,6 +41,7 @@ class UiPathChatMessagesMapper:
4141 def __init__ (self ):
4242 """Initialize the mapper with empty state."""
4343 self .tool_call_to_ai_message : dict [str , str ] = {}
44+ self .current_message : AIMessageChunk
4445 self .seen_message_ids : set [str ] = set ()
4546
4647 def _extract_text (self , content : Any ) -> str :
@@ -141,7 +142,7 @@ def _map_messages_internal(
141142 def map_event (
142143 self ,
143144 message : BaseMessage ,
144- ) -> UiPathConversationMessageEvent | None :
145+ ) -> list [ UiPathConversationMessageEvent ] | None :
145146 """Convert LangGraph BaseMessage (chunk or full) into a UiPathConversationMessageEvent.
146147
147148 Args:
@@ -168,16 +169,45 @@ def map_event(
168169
169170 # Check if this is the last chunk by examining chunk_position
170171 if message .chunk_position == "last" :
172+ events : list [UiPathConversationMessageEvent ] = []
173+
174+ # Loop through all content_blocks in current_message and create toolCallStart events for each tool_call_chunk
175+ if self .current_message and self .current_message .content_blocks :
176+ for block in self .current_message .content_blocks :
177+ if block .get ("type" ) == "tool_call_chunk" :
178+ tool_chunk_block = cast (ToolCallChunk , block )
179+ tool_call_id = tool_chunk_block .get ("id" )
180+ tool_name = tool_chunk_block .get ("name" )
181+ tool_args = tool_chunk_block .get ("args" )
182+
183+ if tool_call_id :
184+ tool_event = UiPathConversationMessageEvent (
185+ message_id = message .id ,
186+ tool_call = UiPathConversationToolCallEvent (
187+ tool_call_id = tool_call_id ,
188+ start = UiPathConversationToolCallStartEvent (
189+ tool_name = tool_name ,
190+ timestamp = timestamp ,
191+ input = UiPathInlineValue (inline = tool_args ),
192+ ),
193+ ),
194+ )
195+ events .append (tool_event )
196+
197+ # Create the final event for the message
171198 msg_event .end = UiPathConversationMessageEndEvent (timestamp = timestamp )
172199 msg_event .content_part = UiPathConversationContentPartEvent (
173200 content_part_id = f"chunk-{ message .id } -0" ,
174201 end = UiPathConversationContentPartEndEvent (),
175202 )
176- return msg_event
203+ events .append (msg_event )
204+
205+ return events
177206
178207 # For every new message_id, start a new message
179208 if message .id not in self .seen_message_ids :
180209 self .seen_message_ids .add (message .id )
210+ self .current_message = message
181211 msg_event .start = UiPathConversationMessageStartEvent (
182212 role = "assistant" , timestamp = timestamp
183213 )
@@ -200,7 +230,6 @@ def map_event(
200230 content_part_id = f"chunk-{ message .id } -0" ,
201231 chunk = UiPathConversationContentPartChunkEvent (
202232 data = text ,
203- content_part_sequence = 0 ,
204233 ),
205234 )
206235
@@ -210,19 +239,10 @@ def map_event(
210239 tool_call_id = tool_chunk_block .get ("id" )
211240 if tool_call_id :
212241 # Track tool_call_id -> ai_message_id mapping
213- self .tool_call_to_ai_message [str (tool_call_id )] = message .id
214-
215- args = tool_chunk_block .get ("args" ) or ""
242+ self .tool_call_to_ai_message [tool_call_id ] = message .id
216243
217- msg_event .content_part = UiPathConversationContentPartEvent (
218- content_part_id = f"chunk-{ message .id } -0" ,
219- chunk = UiPathConversationContentPartChunkEvent (
220- data = args ,
221- content_part_sequence = 0 ,
222- ),
223- )
224- # Continue so that multiple tool_call_chunks in the same block list
225- # are handled correctly
244+ # Accumulate the message chunk
245+ self .current_message = self .current_message + message
226246 continue
227247
228248 # Fallback: raw string content on the chunk (rare when using content_blocks)
@@ -231,7 +251,6 @@ def map_event(
231251 content_part_id = f"content-{ message .id } " ,
232252 chunk = UiPathConversationContentPartChunkEvent (
233253 data = message .content ,
234- content_part_sequence = 0 ,
235254 ),
236255 )
237256
@@ -241,7 +260,7 @@ def map_event(
241260 or msg_event .tool_call
242261 or msg_event .end
243262 ):
244- return msg_event
263+ return [ msg_event ]
245264
246265 return None
247266
@@ -275,35 +294,34 @@ def map_event(
275294 # Keep as string if not valid JSON
276295 pass
277296
278- return UiPathConversationMessageEvent (
279- message_id = result_message_id or str (uuid4 ()),
280- tool_call = UiPathConversationToolCallEvent (
281- tool_call_id = message .tool_call_id ,
282- start = UiPathConversationToolCallStartEvent (
283- tool_name = message .name ,
284- arguments = None ,
285- timestamp = timestamp ,
297+ return [
298+ UiPathConversationMessageEvent (
299+ message_id = result_message_id or str (uuid4 ()),
300+ tool_call = UiPathConversationToolCallEvent (
301+ tool_call_id = message .tool_call_id ,
302+ end = UiPathConversationToolCallEndEvent (
303+ timestamp = timestamp ,
304+ output = UiPathInlineValue (inline = content_value ),
305+ ),
286306 ),
287- end = UiPathConversationToolCallEndEvent (
288- timestamp = timestamp ,
289- output = UiPathInlineValue (inline = content_value ),
290- ),
291- ),
292- )
307+ )
308+ ]
293309
294310 # --- Fallback for other BaseMessage types ---
295311 text_content = self ._extract_text (message .content )
296- return UiPathConversationMessageEvent (
297- message_id = message .id ,
298- start = UiPathConversationMessageStartEvent (
299- role = "assistant" , timestamp = timestamp
300- ),
301- content_part = UiPathConversationContentPartEvent (
302- content_part_id = f"cp-{ message .id } " ,
303- chunk = UiPathConversationContentPartChunkEvent (data = text_content ),
304- ),
305- end = UiPathConversationMessageEndEvent (),
306- )
312+ return [
313+ UiPathConversationMessageEvent (
314+ message_id = message .id ,
315+ start = UiPathConversationMessageStartEvent (
316+ role = "assistant" , timestamp = timestamp
317+ ),
318+ content_part = UiPathConversationContentPartEvent (
319+ content_part_id = f"cp-{ message .id } " ,
320+ chunk = UiPathConversationContentPartChunkEvent (data = text_content ),
321+ ),
322+ end = UiPathConversationMessageEndEvent (),
323+ )
324+ ]
307325
308326
309327__all__ = ["UiPathChatMessagesMapper" ]
0 commit comments