| |
| """ |
| GitEnv Client |
| ------------- |
| Client-side wrapper for the Git environment server. |
| |
| This client maintains a persistent WebSocket connection to the environment |
| server, enabling efficient multi-step interactions with lower latency. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from typing import TYPE_CHECKING |
|
|
| from openenv.core.client_types import StepResult |
| from openenv.core.env_client import EnvClient |
|
|
| from .models import GitAction, GitObservation, GitState |
|
|
| if TYPE_CHECKING: |
| from openenv.core.containers.runtime import ContainerProvider |
|
|
|
|
| class GitEnv(EnvClient[GitAction, GitObservation, GitState]): |
| """ |
| Client for Git Environment with Gitea server. |
| |
| This client maintains a persistent WebSocket connection to the environment |
| server, enabling efficient multi-step interactions for Git operations. |
| |
| The environment connects to a shared external Gitea service. Repositories |
| must be pre-migrated to Gitea before use. |
| |
| Example: |
| >>> # From Docker image |
| >>> client = GitEnv.from_docker_image("git-env:latest") |
| >>> try: |
| ... result = client.reset() |
| ... |
| ... # List available repositories |
| ... from envs.git_env import GitAction |
| ... result = client.step(GitAction(action_type="list_repos")) |
| ... print(result.observation.repos) |
| ... |
| ... # Clone repository to workspace |
| ... result = client.step(GitAction(action_type="clone_repo", repo_name="OpenEnv")) |
| ... |
| ... # Execute git commands |
| ... result = client.step(GitAction( |
| ... action_type="execute_git_command", |
| ... command="status", |
| ... working_dir="OpenEnv" |
| ... )) |
| ... finally: |
| ... client.close() |
| """ |
|
|
| def _step_payload(self, action: GitAction) -> dict: |
| """ |
| Convert action to payload for server's /step endpoint. |
| |
| Args: |
| action: GitAction to send to server |
| |
| Returns: |
| Dictionary payload for HTTP request |
| """ |
| |
| payload = { |
| "action_type": action.action_type, |
| } |
|
|
| |
| if hasattr(action, "repo_name"): |
| payload["repo_name"] = action.repo_name |
| if hasattr(action, "target_dir"): |
| payload["target_dir"] = action.target_dir |
| if hasattr(action, "command"): |
| payload["command"] = action.command |
| if hasattr(action, "working_dir"): |
| payload["working_dir"] = action.working_dir |
|
|
| return payload |
|
|
| def _parse_result(self, payload: dict) -> StepResult[GitObservation]: |
| """ |
| Parse server response into StepResult. |
| |
| Args: |
| payload: JSON response from /step endpoint |
| |
| Returns: |
| StepResult containing GitObservation |
| """ |
| obs = GitObservation(**payload["observation"]) |
| return StepResult( |
| observation=obs, |
| reward=payload.get("reward"), |
| done=bool(payload.get("done", False)), |
| ) |
|
|
| def _parse_state(self, payload: dict) -> GitState: |
| """ |
| Parse server response into GitState object. |
| |
| Args: |
| payload: JSON response from /state endpoint |
| |
| Returns: |
| GitState object with environment state |
| """ |
| return GitState( |
| episode_id=payload.get("episode_id"), |
| step_count=payload.get("step_count", 0), |
| gitea_ready=payload.get("gitea_ready", False), |
| workspace_path=payload.get("workspace_path", "/workspace"), |
| ) |
|
|