# Copyright (c) Microsoft. All rights reserved. import re from typing import TYPE_CHECKING, Any, cast from azure.ai.agents.models import ( MessageDeltaImageFileContent, MessageDeltaImageFileContentObject, MessageDeltaTextContent, MessageDeltaTextFileCitationAnnotation, MessageDeltaTextFilePathAnnotation, MessageDeltaTextUrlCitationAnnotation, MessageImageFileContent, MessageTextContent, MessageTextFileCitationAnnotation, MessageTextFilePathAnnotation, MessageTextUrlCitationAnnotation, RequiredFunctionToolCall, RequiredMcpToolCall, RunStep, RunStepAzureAISearchToolCall, RunStepBingCustomSearchToolCall, RunStepBingGroundingToolCall, RunStepDeepResearchToolCall, RunStepDeltaCodeInterpreterImageOutput, RunStepDeltaCodeInterpreterLogOutput, RunStepDeltaCodeInterpreterToolCall, RunStepDeltaFileSearchToolCall, RunStepDeltaFunctionToolCall, RunStepFileSearchToolCall, RunStepFunctionToolCall, RunStepMcpToolCall, RunStepOpenAPIToolCall, ThreadMessage, ThreadRun, ) from semantic_kernel.contents.annotation_content import AnnotationContent, CitationType from semantic_kernel.contents.chat_message_content import ChatMessageContent from semantic_kernel.contents.file_reference_content import FileReferenceContent from semantic_kernel.contents.function_call_content import FunctionCallContent from semantic_kernel.contents.function_result_content import FunctionResultContent from semantic_kernel.contents.image_content import ImageContent from semantic_kernel.contents.streaming_annotation_content import StreamingAnnotationContent from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent from semantic_kernel.contents.streaming_file_reference_content import StreamingFileReferenceContent from semantic_kernel.contents.streaming_text_content import StreamingTextContent from semantic_kernel.contents.text_content import TextContent from semantic_kernel.contents.utils.author_role import AuthorRole from semantic_kernel.utils.feature_stage_decorator import experimental if TYPE_CHECKING: from azure.ai.agents.models import ( MessageDeltaChunk, RunStepDeltaToolCallObject, ) _URL_PATTERN = re.compile(r"https?://[^\s\]\)]+", re.IGNORECASE) THREAD_MESSAGE_ID = "thread_message_id" """ The methods in this file are used with Azure AI Agent related code. They are used to invoke, create chat messages, or generate message content. """ @experimental def get_message_contents(message: "ChatMessageContent") -> list[dict[str, Any]]: """Get the message contents. Args: message: The message. """ contents: list[dict[str, Any]] = [] for content in message.items: match content: case TextContent(): # Make sure text is a string final_text = content.text if not isinstance(final_text, str): if isinstance(final_text, (list, tuple)): final_text = " ".join(map(str, final_text)) else: final_text = str(final_text) contents.append({"type": "text", "text": final_text}) case ImageContent(): if content.uri: contents.append(content.to_dict()) case FileReferenceContent(): contents.append({ "type": "image_file", "image_file": {"file_id": content.file_id}, }) case FunctionResultContent(): final_result = content.result match final_result: case str(): contents.append({"type": "text", "text": final_result}) case list() | tuple(): contents.append({"type": "text", "text": " ".join(map(str, final_result))}) case _: contents.append({"type": "text", "text": str(final_result)}) return contents @experimental def generate_message_content( assistant_name: str, message: "ThreadMessage", completed_step: "RunStep | None" = None ) -> ChatMessageContent: """Generate message content.""" role = AuthorRole(message.role) metadata = ( { "created_at": completed_step.created_at, "message_id": message.id, # message needs to be defined in context "thread_message_id": message.id, # Add `thread_message_id` to avoid breaking the existing `message_id` key "step_id": completed_step.id, "run_id": completed_step.run_id, "thread_id": completed_step.thread_id, "agent_id": completed_step.agent_id, "usage": completed_step.usage, } if completed_step is not None else None ) content: ChatMessageContent = ChatMessageContent(role=role, name=assistant_name, metadata=metadata) # type: ignore messages: list[MessageImageFileContent | MessageTextContent] = cast( list[MessageImageFileContent | MessageTextContent], message.content or [] ) for item_content in messages: if item_content.type == "text": content.items.append( TextContent( text=item_content.text.value, ) ) for annotation in item_content.text.annotations: content.items.append(generate_annotation_content(annotation)) # type: ignore elif item_content.type == "image_file": content.items.append( FileReferenceContent( file_id=item_content.image_file.file_id, ) ) return content @experimental def generate_streaming_message_content( assistant_name: str, message_delta_event: "MessageDeltaChunk", thread_msg_id: str | None = None, ) -> StreamingChatMessageContent: """Generate streaming message content from a MessageDeltaEvent.""" delta = message_delta_event.delta # Determine the role role = AuthorRole(delta.role) if delta.role is not None else AuthorRole("assistant") items: list[StreamingTextContent | StreamingAnnotationContent | StreamingFileReferenceContent] = [] delta_chunks: list[MessageDeltaImageFileContent | MessageDeltaTextContent] = cast( list[MessageDeltaImageFileContent | MessageDeltaTextContent], delta.content or [] ) for delta_block in delta_chunks: if delta_block.type == "text": if delta_block.text and delta_block.text.value: # Ensure text is not None text_value = delta_block.text.value items.append( StreamingTextContent( text=text_value, choice_index=delta_block.index, ) ) # Process annotations if any if delta_block.text.annotations: for annotation in delta_block.text.annotations or []: if isinstance( annotation, ( MessageDeltaTextFileCitationAnnotation, MessageDeltaTextFilePathAnnotation, MessageDeltaTextUrlCitationAnnotation, ), ): items.append(generate_streaming_annotation_content(annotation)) elif delta_block.type == "image_file": assert isinstance(delta_block, MessageDeltaImageFileContent) # nosec if delta_block.image_file and isinstance(delta_block.image_file, MessageDeltaImageFileContentObject): file_id = delta_block.image_file.file_id items.append( StreamingFileReferenceContent( file_id=file_id, ) ) metadata: dict[str, Any] | None = None if thread_msg_id: metadata = {THREAD_MESSAGE_ID: thread_msg_id} return StreamingChatMessageContent(role=role, name=assistant_name, items=items, choice_index=0, metadata=metadata) # type: ignore @experimental def get_function_call_contents( run: "ThreadRun", function_steps: dict[str, FunctionCallContent] ) -> list[FunctionCallContent]: """Extract function call contents from the run. Args: run: The run. function_steps: The function steps Returns: The list of function call contents. """ function_call_contents: list[FunctionCallContent] = [] required_action = getattr(run, "required_action", None) submit_tool_outputs = getattr(required_action, "submit_tool_outputs", None) if not submit_tool_outputs or not hasattr(submit_tool_outputs, "tool_calls"): return function_call_contents tool_calls = getattr(submit_tool_outputs, "tool_calls", []) if not isinstance(tool_calls, (list, tuple)): return function_call_contents for tool_call in tool_calls: if not isinstance(tool_call, RequiredFunctionToolCall): continue fcc = FunctionCallContent( id=tool_call.id, index=getattr(tool_call, "index", None), name=tool_call.function.name, arguments=tool_call.function.arguments, ) function_call_contents.append(fcc) function_steps[tool_call.id] = fcc return function_call_contents @experimental def generate_function_call_content(agent_name: str, fccs: list[FunctionCallContent]) -> ChatMessageContent: """Generate function call content. Args: agent_name: The agent name. fccs: The function call contents. Returns: ChatMessageContent: The chat message content containing the function call content as the items. """ return ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name, items=fccs) # type: ignore @experimental def generate_function_call_streaming_content( agent_name: str, fccs: list[FunctionCallContent], ) -> StreamingChatMessageContent: """Generate function call content. Args: agent_name: The agent name. fccs: The function call contents. Returns: StreamingChatMessageContent: The chat message content containing the function call content as the items. """ return StreamingChatMessageContent(role=AuthorRole.ASSISTANT, choice_index=0, name=agent_name, items=fccs) # type: ignore @experimental def generate_function_result_content( agent_name: str, function_step: FunctionCallContent, tool_call: "RunStepFunctionToolCall" ) -> ChatMessageContent: """Generate function result content.""" function_call_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.TOOL, name=agent_name) # type: ignore function_call_content.items.append( FunctionResultContent( function_name=function_step.function_name, plugin_name=function_step.plugin_name, id=function_step.id, result=tool_call.function.get("output"), # type: ignore ) ) return function_call_content @experimental def generate_bing_grounding_content( agent_name: str, bing_tool_call: "RunStepBingGroundingToolCall | RunStepBingCustomSearchToolCall" ) -> ChatMessageContent: """Generate function result content related to a Bing Grounding Tool or Bing Custom Search Tool.""" message_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name) # type: ignore # Extract tool details based on the specific tool type if isinstance(bing_tool_call, RunStepBingGroundingToolCall): tool_details = bing_tool_call.bing_grounding elif isinstance(bing_tool_call, RunStepBingCustomSearchToolCall): tool_details = bing_tool_call.bing_custom_search else: # This should never happen with proper typing, but provides safety raise TypeError(f"Unsupported Bing tool call type: {type(bing_tool_call)}") message_content.items.append( FunctionCallContent( id=bing_tool_call.id, name=bing_tool_call.type, function_name=bing_tool_call.type, arguments=tool_details, ) ) return message_content @experimental def generate_azure_ai_search_content( agent_name: str, azure_ai_search_tool_call: "RunStepAzureAISearchToolCall" ) -> ChatMessageContent | None: """Generate function result content related to an Azure AI Search Tool.""" items: list[FunctionCallContent | FunctionResultContent] = [] # Azure AI Search tool call contains both tool call input and output arguments = azure_ai_search_tool_call.azure_ai_search.get("input") if arguments: items.append( FunctionCallContent( id=azure_ai_search_tool_call.id, name=azure_ai_search_tool_call.type, function_name=azure_ai_search_tool_call.type, arguments=arguments, inner_content=azure_ai_search_tool_call, ) ) result = azure_ai_search_tool_call.azure_ai_search.get("output") if result: items.append( FunctionResultContent( function_name=azure_ai_search_tool_call.type, id=azure_ai_search_tool_call.id, result=result, inner_content=azure_ai_search_tool_call, ) ) return ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name, items=items) if items else None # type: ignore @experimental def generate_file_search_content( agent_name: str, file_search_tool_call: "RunStepFileSearchToolCall" ) -> ChatMessageContent: """Generate function result content related to an Azure AI Search Tool.""" message_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name) # type: ignore # Azure AI Search tool call contains both tool call input and output message_content.items.append( FunctionCallContent( id=file_search_tool_call.id, name=file_search_tool_call.type, function_name=file_search_tool_call.type, arguments=file_search_tool_call.file_search.get("ranking_options", None), ) ) message_content.items.append( FunctionResultContent( function_name=file_search_tool_call.type, id=file_search_tool_call.id, result=file_search_tool_call.file_search.get("results", None), ) ) return message_content @experimental def generate_deep_research_content( agent_name: str, deep_research_tool_call: "RunStepDeepResearchToolCall" ) -> ChatMessageContent: """Generate content for a Deep Research tool call. Emits both the tool call (input) and the tool result (output). If URLs are present in the output text, a simple "Citations" section with unique URLs is appended as text. Args: agent_name: The agent name. deep_research_tool_call: The deep research tool call details. Returns: ChatMessageContent summarizing the deep research call and result. """ items: list[FunctionCallContent | FunctionResultContent | TextContent] = [] details = deep_research_tool_call.deep_research # Function call (input) items.append( FunctionCallContent( id=deep_research_tool_call.id, name=deep_research_tool_call.type, function_name=deep_research_tool_call.type, arguments={"input": getattr(details, "input", None)}, inner_content=deep_research_tool_call, ) ) # Function result (output) output_text = getattr(details, "output", None) if output_text: items.append( FunctionResultContent( function_name=deep_research_tool_call.type, id=deep_research_tool_call.id, result=output_text, inner_content=deep_research_tool_call, ) ) # Optional: Append a simple citations section from any URLs in the output urls = _extract_unique_urls(str(output_text)) if urls: citations_lines = ["## Citations"] + [f"{i + 1}. [{u}]({u})" for i, u in enumerate(urls)] items.append(TextContent(text="\n\n" + "\n".join(citations_lines))) return ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name, items=items) # type: ignore def _extract_unique_urls(text: str) -> list[str]: """Extract unique HTTP/HTTPS URLs from text in order of appearance.""" seen: set[str] = set() ordered: list[str] = [] for match in _URL_PATTERN.finditer(text or ""): url = match.group(0) if url not in seen: seen.add(url) ordered.append(url) return ordered @experimental def generate_openapi_content(agent_name: str, openapi_tool_call: RunStepOpenAPIToolCall) -> ChatMessageContent: """Generate ChatMessageContent for a non-streaming OpenAPI tool call.""" tool_id = openapi_tool_call.get("id") tool_type = openapi_tool_call.get("type", "openapi") function: dict[str, Any] = openapi_tool_call.get("function", {}) items: list[FunctionCallContent | FunctionResultContent] = [] arguments = function.get("arguments") if arguments: items.append( FunctionCallContent( id=tool_id, name=tool_type, function_name=function.get("name"), arguments=arguments, ) ) output = function.get("output") if output: items.append( FunctionResultContent( function_name=function.get("name"), id=tool_id, name=tool_type, result=output, ) ) return ChatMessageContent( role=AuthorRole.ASSISTANT, items=items, # type: ignore name=agent_name, ) @experimental def generate_code_interpreter_content(agent_name: str, code: str) -> "ChatMessageContent": """Generate code interpreter content. Args: agent_name: The agent name. code: The code. Returns: ChatMessageContent: The chat message content. """ return ChatMessageContent( role=AuthorRole.ASSISTANT, content=code, name=agent_name, metadata={"code": True}, ) @experimental def generate_streaming_function_content( agent_name: str, step_details: "RunStepDeltaToolCallObject" ) -> "StreamingChatMessageContent | None": """Generate streaming function content. Args: agent_name: The agent name. step_details: The function step. Returns: StreamingChatMessageContent: The chat message content. """ if not step_details.tool_calls: return None items: list[FunctionCallContent] = [] tool_calls: list[ RunStepDeltaCodeInterpreterToolCall | RunStepDeltaFileSearchToolCall | RunStepDeltaFunctionToolCall ] = cast( list[RunStepDeltaCodeInterpreterToolCall | RunStepDeltaFileSearchToolCall | RunStepDeltaFunctionToolCall], step_details.tool_calls or [], ) for tool in tool_calls: if tool.type == "function" and tool.function: items.append( FunctionCallContent( id=tool.id, index=getattr(tool, "index", None), name=tool.function.name, arguments=tool.function.arguments, ) ) return ( StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, items=items, # type: ignore choice_index=0, ) if len(items) > 0 else None ) @experimental def generate_streaming_bing_grounding_content( agent_name: str, step_details: "RunStepDeltaToolCallObject" ) -> StreamingChatMessageContent | None: """Generate StreamingChatMessageContent for Bing Grounding and Bing Custom Search tool calls.""" if not step_details.tool_calls: return None items: list[FunctionCallContent] = [] for index, tool in enumerate(step_details.tool_calls): if tool.type not in ("bing_grounding", "bing_custom_search"): continue # Extract tool details based on the specific tool type if tool.type == "bing_grounding": tool_details = tool.get("bing_grounding", {}) elif tool.type == "bing_custom_search": tool_details = tool.get("bing_custom_search", {}) else: continue request_url = tool_details.get("requesturl", None) response_metadata = tool_details.get("response_metadata", None) if not request_url and not response_metadata: continue items.append( FunctionCallContent( id=tool.id, index=index, name=tool.type, function_name=tool.type, arguments=tool_details, ) ) if not items: return None return StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, choice_index=0, items=items, # type: ignore ) @experimental def generate_streaming_azure_ai_search_content( agent_name: str, step_details: "RunStepDeltaToolCallObject" ) -> StreamingChatMessageContent | None: """Generate function result content related to a Bing Grounding Tool.""" if not step_details.tool_calls: return None items: list[FunctionCallContent | FunctionResultContent] = [] for index, tool in enumerate(step_details.tool_calls): if tool.type == "azure_ai_search": azure_ai_search_tool = cast(RunStepAzureAISearchToolCall, tool) azure_ai_search_dict: dict = azure_ai_search_tool.get("azure_ai_search", None) arguments = azure_ai_search_dict.get("input", {}) if azure_ai_search_dict else None if arguments: items.append( FunctionCallContent( id=azure_ai_search_tool.id, index=index, name=azure_ai_search_tool.type, function_name=azure_ai_search_tool.type, arguments=arguments, inner_content=azure_ai_search_tool, ) ) result = azure_ai_search_dict.get("output", {}) if azure_ai_search_dict else None if result: items.append( FunctionResultContent( function_name=azure_ai_search_tool.type, id=azure_ai_search_tool.id, result=result, inner_content=azure_ai_search_tool, ) ) return ( StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, choice_index=0, items=items, # type: ignore ) if items else None ) # type: ignore @experimental def generate_streaming_deep_research_content( agent_name: str, step_details: "RunStepDeltaToolCallObject" ) -> StreamingChatMessageContent | None: """Generate streaming content related to a Deep Research Tool. Emits FunctionCallContent for the input and FunctionResultContent for the output as they appear in streamed tool call deltas. """ if not step_details.tool_calls: return None items: list[FunctionCallContent | FunctionResultContent] = [] for index, tool in enumerate(step_details.tool_calls): if tool.type == "deep_research": deep_research_dict: dict = tool.get("deep_research", {}) arguments = {"input": deep_research_dict.get("input")} if any(v is not None for v in arguments.values()): items.append( FunctionCallContent( id=tool.get("id"), index=index, name=tool.type, function_name=tool.type, arguments=arguments, ) ) result = deep_research_dict.get("output") if result is not None: items.append( FunctionResultContent( function_name=tool.type, id=tool.get("id"), result=result, ) ) return ( StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, choice_index=0, items=items, # type: ignore ) if items else None ) @experimental def generate_streaming_file_search_content( agent_name: str, step_details: "RunStepDeltaToolCallObject" ) -> StreamingChatMessageContent | None: """Generate function result content related to a File Search Tool.""" if not step_details.tool_calls: return None items: list[FunctionCallContent | FunctionResultContent] = [] for index, tool in enumerate(step_details.tool_calls): if tool.type == "file_search": file_search_tool = cast(RunStepFileSearchToolCall, tool) arguments = getattr(file_search_tool, "file_search", None) results: list[Any] = [] if arguments is not None: results = arguments.pop("results", None) items.append( FunctionCallContent( id=file_search_tool.id, index=index, name=file_search_tool.type, function_name=file_search_tool.type, arguments=arguments, ) ) items.append( FunctionResultContent( function_name=file_search_tool.type, id=file_search_tool.id, name=file_search_tool.type, result=results, ) ) return StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, choice_index=0, items=items, # type: ignore ) @experimental def generate_streaming_openapi_content( agent_name: str, step_details: "RunStepDeltaToolCallObject", ) -> "StreamingChatMessageContent | None": """Generate OpenAPI content for streaming function/tool call messages.""" if not getattr(step_details, "tool_calls", None): return None items: list[FunctionCallContent | FunctionResultContent] = [] # type: ignore for index, tool in enumerate(step_details.tool_calls or []): if tool.get("type") != "openapi": continue func: dict[str, Any] = tool.get("function") tool_id = tool.get("id") arguments = func.get("arguments") if func else None if arguments: items.append( FunctionCallContent( id=tool_id, index=index, name="openapi", function_name=func.get("name") if func else None, arguments=arguments, ) ) output = func.get("output") if func else None if output: items.append( FunctionResultContent( function_name=func.get("name") if func else None, id=tool_id, name="openapi", result=output, ) ) if not items: return None return StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, choice_index=0, items=items, # type: ignore ) @experimental def generate_streaming_code_interpreter_content( agent_name: str, step_details: "RunStepDeltaToolCallObject" ) -> "StreamingChatMessageContent | None": """Generate code interpreter content. Args: agent_name: The agent name. step_details: The current step details. Returns: StreamingChatMessageContent: The chat message content. """ items: list[StreamingTextContent | StreamingFileReferenceContent] = [] if not step_details.tool_calls: return None metadata: dict[str, bool] = {} for index, tool in enumerate(step_details.tool_calls): if isinstance(tool, RunStepDeltaCodeInterpreterToolCall): code_interpreter_tool_call = tool.code_interpreter if code_interpreter_tool_call is None: continue if code_interpreter_tool_call.input: items.append( StreamingTextContent( choice_index=index, text=code_interpreter_tool_call.input, ) ) metadata["code"] = True if code_interpreter_tool_call.outputs: for output in code_interpreter_tool_call.outputs: if ( isinstance(output, RunStepDeltaCodeInterpreterImageOutput) and output.image is not None and output.image.file_id ): items.append( StreamingFileReferenceContent( file_id=output.image.file_id, ) ) if isinstance(output, RunStepDeltaCodeInterpreterLogOutput) and output.logs: items.append( StreamingTextContent( choice_index=index, text=output.logs, ) ) return ( StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, items=items, # type: ignore choice_index=0, metadata=metadata if metadata else None, ) if len(items) > 0 else None ) @experimental def generate_annotation_content( annotation: MessageTextFilePathAnnotation | MessageTextFileCitationAnnotation | MessageTextUrlCitationAnnotation, ) -> AnnotationContent: """Generate annotation content with safe attribute access.""" file_id = None url = None title = None citation_type = None if isinstance(annotation, MessageTextFilePathAnnotation) and annotation.file_path: file_id = annotation.file_path.file_id citation_type = CitationType.FILE_PATH elif isinstance(annotation, MessageTextFileCitationAnnotation) and annotation.file_citation: file_id = annotation.file_citation.file_id citation_type = CitationType.FILE_CITATION elif isinstance(annotation, MessageTextUrlCitationAnnotation) and annotation.url_citation: url = annotation.url_citation.url title = annotation.url_citation.title citation_type = CitationType.URL_CITATION return AnnotationContent( file_id=file_id, quote=getattr(annotation, "text", None), start_index=getattr(annotation, "start_index", None), end_index=getattr(annotation, "end_index", None), url=url, title=title, citation_type=citation_type, ) @experimental def generate_streaming_annotation_content( annotation: MessageDeltaTextFilePathAnnotation | MessageDeltaTextFileCitationAnnotation | MessageDeltaTextUrlCitationAnnotation, ) -> StreamingAnnotationContent: """Generate streaming annotation content with defensive checks.""" file_id = None url = None quote = None title = None citation_type = None if isinstance(annotation, MessageDeltaTextFilePathAnnotation) and annotation.file_path: file_id = annotation.file_path.file_id quote = getattr(annotation, "text", None) citation_type = CitationType.FILE_PATH elif isinstance(annotation, MessageDeltaTextFileCitationAnnotation) and annotation.file_citation: file_id = annotation.file_citation.file_id quote = getattr(annotation, "text", None) citation_type = CitationType.FILE_CITATION elif isinstance(annotation, MessageDeltaTextUrlCitationAnnotation) and annotation.url_citation: url = annotation.url_citation.url title = annotation.url_citation.title quote = annotation.get("text", None) citation_type = CitationType.URL_CITATION return StreamingAnnotationContent( file_id=file_id, quote=quote, start_index=getattr(annotation, "start_index", None), end_index=getattr(annotation, "end_index", None), url=url, title=title, citation_type=citation_type, ) @experimental def generate_mcp_content(agent_name: str, mcp_tool_call: RunStepMcpToolCall) -> ChatMessageContent: """Generate MCP tool content. Args: agent_name: The name of the agent. mcp_tool_call: The MCP tool call. Returns: The generated content. """ mcp_result = FunctionResultContent( function_name=mcp_tool_call.name, id=mcp_tool_call.id, result=mcp_tool_call.output, ) return ChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, items=[mcp_result], inner_content=mcp_tool_call, # type: ignore ) @experimental def generate_mcp_call_content(agent_name: str, mcp_tool_calls: list[RequiredMcpToolCall]) -> ChatMessageContent: """Generate MCP tool call content. Args: agent_name: The name of the agent. mcp_tool_calls: The MCP tool calls. Returns: The generated content. """ content_items: list[FunctionCallContent] = [] for mcp_call in mcp_tool_calls: content_items.append( FunctionCallContent( id=mcp_call.id, name=mcp_call.name, function_name=mcp_call.name, arguments=mcp_call.arguments, server_label=mcp_call.server_label, ) ) return ChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, items=content_items, # type: ignore ) @experimental def generate_streaming_mcp_call_content( agent_name: str, mcp_tool_calls: list["RequiredMcpToolCall"] ) -> "StreamingChatMessageContent | None": """Generate streaming MCP content. Args: agent_name: The name of the agent. mcp_tool_calls: The mcp tool call details. Returns: The generated streaming content. """ items: list[FunctionCallContent] = [] for index, tool in enumerate(mcp_tool_calls or []): if isinstance(tool, RequiredMcpToolCall): items.append( FunctionCallContent( id=tool.id, index=index, name=tool.name, function_name=tool.name, arguments=tool.arguments, server_label=tool.server_label, ) ) return ( StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, items=items, # type: ignore choice_index=0, ) if items else None ) @experimental def generate_streaming_mcp_content( agent_name: str, step_details: "RunStepDeltaToolCallObject" ) -> StreamingChatMessageContent | None: """Generate MCP tool content. Args: agent_name: The name of the agent. step_details: The steps details with mcp tool call. Returns: The generated content. """ if not step_details.tool_calls: return None items: list[FunctionResultContent] = [] for _, tool in enumerate(step_details.tool_calls): if tool.type == "mcp": mcp_tool_call = cast(RunStepMcpToolCall, tool) if not mcp_tool_call.get("output"): continue mcp_result = FunctionResultContent( function_name=mcp_tool_call.get("name"), id=mcp_tool_call.get("id"), result=mcp_tool_call.get("output"), ) items.append(mcp_result) return ( StreamingChatMessageContent( role=AuthorRole.ASSISTANT, name=agent_name, items=items, # type: ignore inner_content=mcp_tool_call, # type: ignore choice_index=0, ) if items else None ) # type: ignore