# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors # # SPDX-License-Identifier: Apache-2.0 from abc import ABC from copy import deepcopy from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union from uuid import UUID import re from pydantic import BaseModel, field_validator, model_serializer from termcolor import colored from autogen.agentchat.group import ContextVariables from ..agentchat.agent import LLMMessageType from ..code_utils import content_str from ..import_utils import optional_import_block, require_optional_import from ..oai.client import OpenAIWrapper from .base_event import BaseEvent, wrap_event with optional_import_block() as result: from PIL.Image import Image IS_PIL_AVAILABLE = result.is_successful if TYPE_CHECKING: from ..agentchat.agent import Agent from ..coding.base import CodeBlock __all__ = [ "ClearAgentsHistoryEvent", "ClearConversableAgentHistoryEvent", "ConversableAgentUsageSummaryEvent", "ConversableAgentUsageSummaryNoCostIncurredEvent", "ExecuteCodeBlockEvent", "ExecuteFunctionEvent", "FunctionCallEvent", "FunctionResponseEvent", "GenerateCodeExecutionReplyEvent", "GroupChatResumeEvent", "GroupChatRunChatEvent", "PostCarryoverProcessingEvent", "SelectSpeakerEvent", "SpeakerAttemptFailedMultipleAgentsEvent", "SpeakerAttemptFailedNoAgentsEvent", "SpeakerAttemptSuccessfulEvent", "TerminationAndHumanReplyNoInputEvent", "TerminationEvent", "TextEvent", "ToolCallEvent", "ToolResponseEvent", ] EventRole = Literal["assistant", "function", "tool"] class BasePrintReceivedEvent(BaseEvent, ABC): content: Union[str, int, float, bool] sender: str recipient: str def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(f"{colored(self.sender, 'yellow')} (to {self.recipient}):\n", flush=True) @wrap_event class FunctionResponseEvent(BasePrintReceivedEvent): name: Optional[str] = None role: EventRole = "function" content: Union[str, int, float, bool] def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print super().print(f) id = self.name or "No id found" func_print = f"***** Response from calling {self.role} ({id}) *****" f(colored(func_print, "green"), flush=True) f(self.content, flush=True) f(colored("*" * len(func_print), "green"), flush=True) f("\n", "-" * 80, flush=True, sep="") class ToolResponse(BaseModel): tool_call_id: Optional[str] = None role: EventRole = "tool" content: Union[str, int, float, bool] def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print id = self.tool_call_id or "No id found" tool_print = f"***** Response from calling {self.role} ({id}) *****" f(colored(tool_print, "green"), flush=True) f(re.sub(r']*>', '', self.content), flush=True) f(colored("*" * len(tool_print), "green"), flush=True) @wrap_event class ToolResponseEvent(BasePrintReceivedEvent): role: EventRole = "tool" tool_responses: list[ToolResponse] content: Optional[Union[str, int, float, bool, list[dict[str, Any]]]] def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print super().print(f) for tool_response in self.tool_responses: tool_response.print(f) f("\n", "-" * 80, flush=True, sep="") class FunctionCall(BaseModel): name: Optional[str] = None arguments: Optional[str] = None def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print name = self.name or "(No function name found)" arguments = self.arguments or "(No arguments found)" func_print = f"***** Suggested function call: {name} *****" f(colored(func_print, "green"), flush=True) f( "Arguments: \n", arguments, flush=True, sep="", ) f(colored("*" * len(func_print), "green"), flush=True) @wrap_event class FunctionCallEvent(BasePrintReceivedEvent): content: Optional[Union[str, int, float, bool]] = None # type: ignore [assignment] function_call: FunctionCall def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print super().print(f) if self.content is not None: f(self.content, flush=True) self.function_call.print(f) f("\n", "-" * 80, flush=True, sep="") class ToolCall(BaseModel): id: Optional[str] = None function: FunctionCall type: str def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print id = self.id or "No tool call id found" name = self.function.name or "(No function name found)" arguments = self.function.arguments or "(No arguments found)" func_print = f"***** Suggested tool call ({id}): {name} *****" f(colored(func_print, "green"), flush=True) f( "Arguments: \n", arguments, flush=True, sep="", ) f(colored("*" * len(func_print), "green"), flush=True) @wrap_event class ToolCallEvent(BasePrintReceivedEvent): content: Optional[Union[str, int, float, bool, list[dict[str, Any]]]] = None # type: ignore [assignment] refusal: Optional[str] = None role: Optional[EventRole] = None audio: Optional[str] = None function_call: Optional[FunctionCall] = None tool_calls: list[ToolCall] def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print super().print(f) if self.content is not None: f(self.content[0]['text'], flush=True) for tool_call in self.tool_calls: tool_call.print(f) f("\n", "-" * 80, flush=True, sep="") @wrap_event class TextEvent(BasePrintReceivedEvent): content: Optional[Union[str, int, float, bool, list[dict[str, Union[str, Union[str, dict[str, Any]]]]]]] = None # type: ignore [assignment] @classmethod @require_optional_import("PIL", "unknown") def _replace_pil_image_with_placeholder(cls, item: dict[str, Any], key: str) -> None: if isinstance(item[key], Image): item[key] = "" @field_validator("content", mode="before") @classmethod def validate_and_encode_content( cls, content: Optional[Union[str, int, float, bool, list[dict[str, Union[str, dict[str, Any]]]]]] ) -> Optional[Union[str, int, float, bool, list[dict[str, Union[str, dict[str, Any]]]]]]: if not IS_PIL_AVAILABLE: return content if not isinstance(content, list): return content for item in content: if isinstance(item, dict) and "image_url" in item: cls._replace_pil_image_with_placeholder(item, "image_url") return content def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print super().print(f) if self.content is not None: f(content_str(self.content), flush=True) # type: ignore [arg-type] f("\n", "-" * 80, flush=True, sep="") def create_received_event_model( *, uuid: Optional[UUID] = None, event: dict[str, Any], sender: "Agent", recipient: "Agent" ) -> Union[FunctionResponseEvent, ToolResponseEvent, FunctionCallEvent, ToolCallEvent, TextEvent]: role = event.get("role") if role == "function": return FunctionResponseEvent(**event, sender=sender.name, recipient=recipient.name, uuid=uuid) if role == "tool": return ToolResponseEvent(**event, sender=sender.name, recipient=recipient.name, uuid=uuid) # Role is neither function nor tool if event.get("function_call"): return FunctionCallEvent( **event, sender=sender.name, recipient=recipient.name, uuid=uuid, ) if event.get("tool_calls"): return ToolCallEvent( **event, sender=sender.name, recipient=recipient.name, uuid=uuid, ) # Now message is a simple content message content = event.get("content") allow_format_str_template = ( recipient.llm_config.get("allow_format_str_template", False) if recipient.llm_config else False # type: ignore [attr-defined] ) if content is not None and "context" in event: content = OpenAIWrapper.instantiate( content, # type: ignore [arg-type] event["context"], allow_format_str_template, ) return TextEvent( content=content, sender=sender.name, recipient=recipient.name, uuid=uuid, ) @wrap_event class PostCarryoverProcessingEvent(BaseEvent): carryover: Union[str, list[Union[str, dict[str, Any], Any]]] message: str verbose: bool = False sender: str recipient: str summary_method: str summary_args: Optional[dict[str, Any]] = None max_turns: Optional[int] = None def __init__(self, *, uuid: Optional[UUID] = None, chat_info: dict[str, Any]): carryover = chat_info.get("carryover", "") message = chat_info.get("message") verbose = chat_info.get("verbose", False) sender = chat_info["sender"].name if hasattr(chat_info["sender"], "name") else chat_info["sender"] recipient = chat_info["recipient"].name if hasattr(chat_info["recipient"], "name") else chat_info["recipient"] summary_args = chat_info.get("summary_args") max_turns = chat_info.get("max_turns") # Fix Callable in chat_info summary_method = chat_info.get("summary_method", "") if callable(summary_method): summary_method = summary_method.__name__ print_message = "" if isinstance(message, str): print_message = message elif callable(message): print_message = "Callable: " + message.__name__ elif isinstance(message, dict): print_message = "Dict: " + str(message) elif message is None: print_message = "None" super().__init__( uuid=uuid, carryover=carryover, message=print_message, verbose=verbose, summary_method=summary_method, summary_args=summary_args, max_turns=max_turns, sender=sender, recipient=recipient, ) @model_serializer def serialize_model(self) -> dict[str, Any]: return { "uuid": self.uuid, "chat_info": { "carryover": self.carryover, "message": self.message, "verbose": self.verbose, "sender": self.sender, "recipient": self.recipient, "summary_method": self.summary_method, "summary_args": self.summary_args, "max_turns": self.max_turns, }, } def _process_carryover(self) -> str: if not isinstance(self.carryover, list): return self.carryover print_carryover = [] for carryover_item in self.carryover: if isinstance(carryover_item, str): print_carryover.append(carryover_item) elif isinstance(carryover_item, dict) and "content" in carryover_item: print_carryover.append(str(carryover_item["content"])) else: print_carryover.append(str(carryover_item)) return ("\n").join(print_carryover) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print print_carryover = self._process_carryover() f(colored("\n" + "*" * 80, "blue"), flush=True, sep="") f( colored( "Starting a new chat....", "blue", ), flush=True, ) if self.verbose: f(colored("Event:\n" + self.message, "blue"), flush=True) f(colored("Carryover:\n" + print_carryover, "blue"), flush=True) f(colored("\n" + "*" * 80, "blue"), flush=True, sep="") @wrap_event class ClearAgentsHistoryEvent(BaseEvent): agent: Optional[str] = None nr_events_to_preserve: Optional[int] = None def __init__( self, *, uuid: Optional[UUID] = None, agent: Optional[Union["Agent", str]] = None, nr_events_to_preserve: Optional[int] = None, ): return super().__init__( uuid=uuid, agent=agent.name if hasattr(agent, "name") else agent, nr_events_to_preserve=nr_events_to_preserve, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print if self.agent: if self.nr_events_to_preserve: f(f"Clearing history for {self.agent} except last {self.nr_events_to_preserve} events.") else: f(f"Clearing history for {self.agent}.") else: if self.nr_events_to_preserve: f(f"Clearing history for all agents except last {self.nr_events_to_preserve} events.") else: f("Clearing history for all agents.") # todo: break into multiple events @wrap_event class SpeakerAttemptSuccessfulEvent(BaseEvent): mentions: dict[str, int] attempt: int attempts_left: int verbose: Optional[bool] = False def __init__( self, *, uuid: Optional[UUID] = None, mentions: dict[str, int], attempt: int, attempts_left: int, select_speaker_auto_verbose: Optional[bool] = False, ): super().__init__( uuid=uuid, mentions=deepcopy(mentions), attempt=attempt, attempts_left=attempts_left, verbose=select_speaker_auto_verbose, ) @model_serializer def serialize_model(self) -> dict[str, Any]: return { "uuid": self.uuid, "mentions": self.mentions, "attempt": self.attempt, "attempts_left": self.attempts_left, "select_speaker_auto_verbose": self.verbose, } def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print selected_agent_name = next(iter(self.mentions)) f( colored( f">>>>>>>> Select speaker attempt {self.attempt} of {self.attempt + self.attempts_left} successfully selected: {selected_agent_name}", "green", ), flush=True, ) @wrap_event class SpeakerAttemptFailedMultipleAgentsEvent(BaseEvent): mentions: dict[str, int] attempt: int attempts_left: int verbose: Optional[bool] = False def __init__( self, *, uuid: Optional[UUID] = None, mentions: dict[str, int], attempt: int, attempts_left: int, select_speaker_auto_verbose: Optional[bool] = False, ): super().__init__( uuid=uuid, mentions=deepcopy(mentions), attempt=attempt, attempts_left=attempts_left, verbose=select_speaker_auto_verbose, ) @model_serializer def serialize_model(self) -> dict[str, Any]: return { "uuid": self.uuid, "mentions": self.mentions, "attempt": self.attempt, "attempts_left": self.attempts_left, "select_speaker_auto_verbose": self.verbose, } def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f( colored( f">>>>>>>> Select speaker attempt {self.attempt} of {self.attempt + self.attempts_left} failed as it included multiple agent names.", "red", ), flush=True, ) @wrap_event class SpeakerAttemptFailedNoAgentsEvent(BaseEvent): mentions: dict[str, int] attempt: int attempts_left: int verbose: Optional[bool] = False def __init__( self, *, uuid: Optional[UUID] = None, mentions: dict[str, int], attempt: int, attempts_left: int, select_speaker_auto_verbose: Optional[bool] = False, ): super().__init__( uuid=uuid, mentions=deepcopy(mentions), attempt=attempt, attempts_left=attempts_left, verbose=select_speaker_auto_verbose, ) @model_serializer def serialize_model(self) -> dict[str, Any]: return { "uuid": self.uuid, "mentions": self.mentions, "attempt": self.attempt, "attempts_left": self.attempts_left, "select_speaker_auto_verbose": self.verbose, } def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f( colored( f">>>>>>>> Select speaker attempt #{self.attempt} failed as it did not include any agent names.", "red", ), flush=True, ) @wrap_event class GroupChatResumeEvent(BaseEvent): last_speaker_name: str events: list[LLMMessageType] verbose: Optional[bool] = False def __init__( self, *, uuid: Optional[UUID] = None, last_speaker_name: str, events: list["LLMMessageType"], silent: Optional[bool] = False, ): super().__init__(uuid=uuid, last_speaker_name=last_speaker_name, events=events, verbose=not silent) @model_serializer def serialize_model(self) -> dict[str, Any]: return { "uuid": self.uuid, "last_speaker_name": self.last_speaker_name, "events": self.events, "silent": not self.verbose, } def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f( f"Prepared group chat with {len(self.events)} events, the last speaker is", colored(self.last_speaker_name, "yellow"), flush=True, ) @wrap_event class GroupChatRunChatEvent(BaseEvent): speaker: str verbose: Optional[bool] = False def __init__(self, *, uuid: Optional[UUID] = None, speaker: Union["Agent", str], silent: Optional[bool] = False): super().__init__(uuid=uuid, speaker=speaker.name if hasattr(speaker, "name") else speaker, verbose=not silent) @model_serializer def serialize_model(self) -> dict[str, Any]: return {"uuid": self.uuid, "speaker": self.speaker, "silent": not self.verbose} def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(colored(f"\nNext speaker: {self.speaker}\n", "green"), flush=True) @wrap_event class TerminationAndHumanReplyNoInputEvent(BaseEvent): """When the human-in-the-loop is prompted but provides no input.""" no_human_input_msg: str sender: str recipient: str def __init__( self, *, uuid: Optional[UUID] = None, no_human_input_msg: str, sender: Optional[Union["Agent", str]] = None, recipient: Union["Agent", str], ): sender = sender or "No sender" super().__init__( uuid=uuid, no_human_input_msg=no_human_input_msg, sender=sender.name if hasattr(sender, "name") else sender, recipient=recipient.name if hasattr(recipient, "name") else recipient, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(colored(f"\n>>>>>>>> {self.no_human_input_msg}", "red"), flush=True) @wrap_event class UsingAutoReplyEvent(BaseEvent): human_input_mode: str sender: str recipient: str def __init__( self, *, uuid: Optional[UUID] = None, human_input_mode: str, sender: Optional[Union["Agent", str]] = None, recipient: Union["Agent", str], ): sender = sender or "No sender" super().__init__( uuid=uuid, human_input_mode=human_input_mode, sender=sender.name if hasattr(sender, "name") else sender, recipient=recipient.name if hasattr(recipient, "name") else recipient, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(colored("\n>>>>>>>> USING AUTO REPLY...", "red"), flush=True) @wrap_event class TerminationEvent(BaseEvent): """When a workflow termination condition is met""" termination_reason: str def __init__( self, *, uuid: Optional[UUID] = None, termination_reason: str, ): super().__init__( uuid=uuid, termination_reason=termination_reason, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(colored(f"\n>>>>>>>> TERMINATING RUN ({str(self.uuid)}): {self.termination_reason}", "red"), flush=True) @wrap_event class ExecuteCodeBlockEvent(BaseEvent): code: str language: str code_block_count: int recipient: str def __init__( self, *, uuid: Optional[UUID] = None, code: str, language: str, code_block_count: int, recipient: Union["Agent", str], ): super().__init__( uuid=uuid, code=code, language=language, code_block_count=code_block_count, recipient=recipient.name if hasattr(recipient, "name") else recipient, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f( colored( f"\n>>>>>>>> EXECUTING CODE BLOCK {self.code_block_count} (inferred language is {self.language})...", "red", ), flush=True, ) @wrap_event class ExecuteFunctionEvent(BaseEvent): func_name: str call_id: Optional[str] = None arguments: dict[str, Any] recipient: str def __init__( self, *, uuid: Optional[UUID] = None, func_name: str, call_id: Optional[str] = None, arguments: dict[str, Any], recipient: Union["Agent", str], ): super().__init__( uuid=uuid, func_name=func_name, call_id=call_id, arguments=arguments, recipient=recipient.name if hasattr(recipient, "name") else recipient, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f( colored( f"\n>>>>>>>> EXECUTING FUNCTION {self.func_name}...\nCall ID: {self.call_id}\nInput arguments: {self.arguments}", "magenta", ), flush=True, ) @wrap_event class ExecutedFunctionEvent(BaseEvent): func_name: str call_id: Optional[str] = None arguments: Optional[dict[str, Any]] content: Any recipient: str is_exec_success: bool = True def __init__( self, *, uuid: Optional[UUID] = None, func_name: str, call_id: Optional[str] = None, arguments: Optional[dict[str, Any]], content: Any, recipient: Union["Agent", str], is_exec_success: bool = True, ): super().__init__( uuid=uuid, func_name=func_name, call_id=call_id, arguments=arguments, content=content, recipient=recipient.name if hasattr(recipient, "name") else recipient, ) self.is_exec_success = is_exec_success def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f( colored( f"\n>>>>>>>> EXECUTED FUNCTION {self.func_name}...\nCall ID: {self.call_id}\nInput arguments: {self.arguments}\nOutput:\n{re.sub(r']*>', '', self.content)}", "magenta", ), flush=True, ) @wrap_event class SelectSpeakerEvent(BaseEvent): agents: Optional[list[str]] = None def __init__(self, *, uuid: Optional[UUID] = None, agents: Optional[list[Union["Agent", str]]] = None): agents = [agent.name if hasattr(agent, "name") else agent for agent in agents] if agents else None super().__init__(uuid=uuid, agents=agents) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f("Please select the next speaker from the following list:") agents = self.agents or [] for i, agent in enumerate(agents): f(f"{i + 1}: {agent}") @wrap_event class SelectSpeakerTryCountExceededEvent(BaseEvent): try_count: int agents: Optional[list[str]] = None def __init__( self, *, uuid: Optional[UUID] = None, try_count: int, agents: Optional[list[Union["Agent", str]]] = None ): agents = [agent.name if hasattr(agent, "name") else agent for agent in agents] if agents else None super().__init__(uuid=uuid, try_count=try_count, agents=agents) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(f"You have tried {self.try_count} times. The next speaker will be selected automatically.") @wrap_event class SelectSpeakerInvalidInputEvent(BaseEvent): agents: Optional[list[str]] = None def __init__(self, *, uuid: Optional[UUID] = None, agents: Optional[list[Union["Agent", str]]] = None): agents = [agent.name if hasattr(agent, "name") else agent for agent in agents] if agents else None super().__init__(uuid=uuid, agents=agents) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(f"Invalid input. Please enter a number between 1 and {len(self.agents or [])}.") @wrap_event class ClearConversableAgentHistoryEvent(BaseEvent): agent: str recipient: str no_events_preserved: int def __init__( self, *, uuid: Optional[UUID] = None, agent: Union["Agent", str], no_events_preserved: Optional[int] = None ): super().__init__( uuid=uuid, agent=agent.name if hasattr(agent, "name") else agent, recipient=agent.name if hasattr(agent, "name") else agent, no_events_preserved=no_events_preserved, ) @model_serializer def serialize_model(self) -> dict[str, Any]: return { "uuid": self.uuid, "agent": self.agent, "no_events_preserved": self.no_events_preserved, } def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print for _ in range(self.no_events_preserved): f(f"Preserving one more event for {self.agent} to not divide history between tool call and tool response.") @wrap_event class ClearConversableAgentHistoryWarningEvent(BaseEvent): recipient: str def __init__(self, *, uuid: Optional[UUID] = None, recipient: Union["Agent", str]): super().__init__( uuid=uuid, recipient=recipient.name if hasattr(recipient, "name") else recipient, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f( colored( "WARNING: `nr_preserved_events` is ignored when clearing chat history with a specific agent.", "yellow", ), flush=True, ) @wrap_event class GenerateCodeExecutionReplyEvent(BaseEvent): code_blocks: list[str] sender: str recipient: str def __init__( self, *, uuid: Optional[UUID] = None, code_blocks: list[Union["CodeBlock", str]], sender: Optional[Union["Agent", str]] = None, recipient: Union["Agent", str], ): code_blocks = [ code_block.language if hasattr(code_block, "language") else code_block for code_block in code_blocks ] sender = sender or "No sender" super().__init__( uuid=uuid, code_blocks=code_blocks, sender=sender.name if hasattr(sender, "name") else sender, recipient=recipient.name if hasattr(recipient, "name") else recipient, ) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print num_code_blocks = len(self.code_blocks) if num_code_blocks == 1: f( colored( f"\n>>>>>>>> EXECUTING CODE BLOCK (inferred language is {self.code_blocks[0]})...", "red", ), flush=True, ) else: f( colored( f"\n>>>>>>>> EXECUTING {num_code_blocks} CODE BLOCKS (inferred languages are [{', '.join([x for x in self.code_blocks])}])...", "red", ), flush=True, ) @wrap_event class ConversableAgentUsageSummaryNoCostIncurredEvent(BaseEvent): recipient: str def __init__(self, *, uuid: Optional[UUID] = None, recipient: Union["Agent", str]): super().__init__(uuid=uuid, recipient=recipient.name if hasattr(recipient, "name") else recipient) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(f"No cost incurred from agent '{self.recipient}'.") @wrap_event class ConversableAgentUsageSummaryEvent(BaseEvent): recipient: str def __init__(self, *, uuid: Optional[UUID] = None, recipient: Union["Agent", str]): super().__init__(uuid=uuid, recipient=recipient.name if hasattr(recipient, "name") else recipient) def print(self, f: Optional[Callable[..., Any]] = None) -> None: f = f or print f(f"Agent '{self.recipient}':") @wrap_event class InputRequestEvent(BaseEvent): prompt: str password: bool = False respond: Optional[Callable[[str], None]] = None type: str = "input_request" @wrap_event class AsyncInputRequestEvent(BaseEvent): prompt: str password: bool = False async def a_respond(self, response: "InputResponseEvent") -> None: pass @wrap_event class InputResponseEvent(BaseEvent): value: str @wrap_event class ErrorEvent(BaseEvent): error: Any @wrap_event class RunCompletionEvent(BaseEvent): summary: str history: list[LLMMessageType] cost: dict[str, Any] last_speaker: Optional[str] context_variables: Optional[ContextVariables] = None