"""Defender RL Environment Client.""" from typing import Dict from openenv.core import EnvClient from openenv.core.client_types import StepResult from openenv.core.env_server.types import State from .models import DefenderAction, DefenderObservation class DefenderEnv(EnvClient[DefenderAction, DefenderObservation, State]): """Client for the Defender RL Environment. Maintains a persistent WebSocket connection. Each client instance gets its own dedicated environment session on the server (factory mode + concurrency). Example: >>> with DefenderEnv(base_url="http://localhost:8000") as client: ... result = client.reset() ... print(result.observation.attacker_message) ... result = client.step(DefenderAction(response="I can't share that.")) ... print(result.reward, result.done) """ def _step_payload(self, action: DefenderAction) -> Dict: return {"response": action.response} def _parse_result(self, payload: Dict) -> StepResult[DefenderObservation]: obs_data = payload.get("observation", {}) observation = DefenderObservation( attacker_message=obs_data.get("attacker_message", ""), conversation_history=obs_data.get("conversation_history", []), turn_index=obs_data.get("turn_index", 0), system_prompt=obs_data.get("system_prompt", ""), scenario_id=obs_data.get("scenario_id", ""), secret_type=obs_data.get("secret_type", ""), attacker_id=obs_data.get("attacker_id", ""), leak_occurred=obs_data.get("leak_occurred", False), leaked_piece=obs_data.get("leaked_piece", ""), done=payload.get("done", False), reward=payload.get("reward"), ) 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), )