"""Pydantic action models for ForgeEnv (compatible with OpenEnv 0.2.x). Episodes have two phases — drift_gen (Challenger) and repair (Solver) — so we expose a single union ForgeAction that carries either a BreakageAction or a RepairAction. The environment dispatches on which sub-field is set. """ from __future__ import annotations from typing import Any, Literal, Optional from pydantic import Field from openenv.core import Action class BreakageAction(Action): """Drift Generator's action: pick a primitive type + parameters.""" action_type: Literal["breakage"] = "breakage" primitive_type: str = Field( ..., description="One of the registered breakage primitive class names" ) params: dict[str, Any] = Field( default_factory=dict, description="Primitive-specific parameters" ) class RepairAction(Action): """Repair Agent's action: a unified diff (or full replacement script).""" action_type: Literal["repair"] = "repair" unified_diff: str = Field(..., description="Unified diff or full replacement script") class ForgeAction(Action): """Union action: exactly one of `breakage` / `repair` must be set. This is the type registered with OpenEnv's `create_app`. It avoids Pydantic discriminated unions to keep the OpenAPI schema flat and cross-version-friendly. """ breakage: Optional[BreakageAction] = None repair: Optional[RepairAction] = None def model_post_init(self, __context: Any) -> None: if (self.breakage is None) == (self.repair is None): raise ValueError( "ForgeAction requires exactly one of `breakage` or `repair` to be set." )