import copy from typing import Any from openhands.events.event import RecallType from openhands.events.observation.agent import ( AgentCondensationObservation, AgentStateChangedObservation, AgentThinkObservation, MicroagentKnowledge, RecallObservation, ) from openhands.events.observation.browse import BrowserOutputObservation from openhands.events.observation.commands import ( CmdOutputMetadata, CmdOutputObservation, IPythonRunCellObservation, ) from openhands.events.observation.delegate import AgentDelegateObservation from openhands.events.observation.empty import ( NullObservation, ) from openhands.events.observation.error import ErrorObservation from openhands.events.observation.files import ( FileEditObservation, FileReadObservation, FileWriteObservation, ) from openhands.events.observation.mcp import MCPObservation from openhands.events.observation.observation import Observation from openhands.events.observation.reject import UserRejectObservation from openhands.events.observation.success import SuccessObservation observations = ( NullObservation, CmdOutputObservation, IPythonRunCellObservation, BrowserOutputObservation, FileReadObservation, FileWriteObservation, FileEditObservation, AgentDelegateObservation, SuccessObservation, ErrorObservation, AgentStateChangedObservation, UserRejectObservation, AgentCondensationObservation, AgentThinkObservation, RecallObservation, MCPObservation, ) OBSERVATION_TYPE_TO_CLASS = { observation_class.observation: observation_class # type: ignore[attr-defined] for observation_class in observations } def _update_cmd_output_metadata( metadata: dict[str, Any] | CmdOutputMetadata | None, **kwargs: Any ) -> dict[str, Any] | CmdOutputMetadata: """Update the metadata of a CmdOutputObservation. If metadata is None, create a new CmdOutputMetadata instance. If metadata is a dict, update the dict. If metadata is a CmdOutputMetadata instance, update the instance. """ if metadata is None: return CmdOutputMetadata(**kwargs) if isinstance(metadata, dict): metadata.update(**kwargs) elif isinstance(metadata, CmdOutputMetadata): for key, value in kwargs.items(): setattr(metadata, key, value) return metadata def handle_observation_deprecated_extras(extras: dict) -> dict: # These are deprecated in https://github.com/All-Hands-AI/OpenHands/pull/4881 if 'exit_code' in extras: extras['metadata'] = _update_cmd_output_metadata( extras.get('metadata', None), exit_code=extras.pop('exit_code') ) if 'command_id' in extras: extras['metadata'] = _update_cmd_output_metadata( extras.get('metadata', None), pid=extras.pop('command_id') ) # formatted_output_and_error has been deprecated in https://github.com/All-Hands-AI/OpenHands/pull/6671 if 'formatted_output_and_error' in extras: extras.pop('formatted_output_and_error') return extras def observation_from_dict(observation: dict) -> Observation: observation = observation.copy() if 'observation' not in observation: raise KeyError(f"'observation' key is not found in {observation=}") observation_class = OBSERVATION_TYPE_TO_CLASS.get(observation['observation']) if observation_class is None: raise KeyError( f"'{observation['observation']=}' is not defined. Available observations: {OBSERVATION_TYPE_TO_CLASS.keys()}" ) observation.pop('observation') observation.pop('message', None) content = observation.pop('content', '') extras = copy.deepcopy(observation.pop('extras', {})) extras = handle_observation_deprecated_extras(extras) # convert metadata to CmdOutputMetadata if it is a dict if observation_class is CmdOutputObservation: if 'metadata' in extras and isinstance(extras['metadata'], dict): extras['metadata'] = CmdOutputMetadata(**extras['metadata']) elif 'metadata' in extras and isinstance(extras['metadata'], CmdOutputMetadata): pass else: extras['metadata'] = CmdOutputMetadata() if observation_class is RecallObservation: # handle the Enum conversion if 'recall_type' in extras: extras['recall_type'] = RecallType(extras['recall_type']) # convert dicts in microagent_knowledge to MicroagentKnowledge objects if 'microagent_knowledge' in extras and isinstance( extras['microagent_knowledge'], list ): extras['microagent_knowledge'] = [ MicroagentKnowledge(**item) if isinstance(item, dict) else item for item in extras['microagent_knowledge'] ] obs = observation_class(content=content, **extras) assert isinstance(obs, Observation) return obs