"""ForensicShell Environment Client.""" from typing import Any, Dict from openenv.core import EnvClient from openenv.core.client_types import StepResult from openenv.core.env_server.types import State from .models import ForensicShellAction, ForensicShellObservation class ForensicShellEnv( EnvClient[ForensicShellAction, ForensicShellObservation, State] ): """ Client for the ForensicShell Environment. Async usage: >>> async with ForensicShellEnv(base_url="http://localhost:8000") as client: ... result = await client.reset() ... result = await client.step( ... ForensicShellAction(action_type="list_dir", path="/var/log") ... ) Docker usage: >>> client = ForensicShellEnv.from_docker_image("forensic-shell:latest") >>> result = await client.reset() """ def _step_payload(self, action: ForensicShellAction) -> Dict[str, Any]: # Full Pydantic dump so nested report/TimelineEvent serializes correctly. return action.model_dump(mode="json", exclude_none=False) def _parse_result(self, payload: Dict) -> StepResult[ForensicShellObservation]: obs_data = payload.get("observation", {}) or {} observation = ForensicShellObservation( output=obs_data.get("output", ""), task_id=obs_data.get("task_id", ""), task_description=obs_data.get("task_description", ""), steps_remaining=obs_data.get("steps_remaining", 0), action_error=obs_data.get("action_error"), done=payload.get("done", False), reward=payload.get("reward"), metadata=obs_data.get("metadata", {}) or {}, ) return StepResult( observation=observation, reward=payload.get("reward"), done=payload.get("done", False), ) def _parse_state(self, payload: Dict) -> State: return State( episode_id=payload.get("episode_id"), step_count=payload.get("step_count", 0), )