zkwentz commited on
Commit
34ed582
·
verified ·
1 Parent(s): d23d0a5

Upload folder using huggingface_hub

Browse files
Files changed (44) hide show
  1. Dockerfile +10 -3
  2. README.md +122 -26
  3. src/core/README.md +180 -0
  4. src/core/__init__.py +1 -1
  5. src/core/__pycache__/__init__.cpython-311.pyc +0 -0
  6. src/core/__pycache__/__init__.cpython-313.pyc +0 -0
  7. src/core/__pycache__/http_env_client.cpython-311.pyc +0 -0
  8. src/core/__pycache__/http_env_client.cpython-313.pyc +0 -0
  9. src/core/__pycache__/types.cpython-311.pyc +0 -0
  10. src/core/__pycache__/types.cpython-313.pyc +0 -0
  11. src/core/client_types.py +22 -0
  12. src/core/containers/__pycache__/__init__.cpython-311.pyc +0 -0
  13. src/core/containers/__pycache__/__init__.cpython-313.pyc +0 -0
  14. src/core/containers/runtime/__pycache__/__init__.cpython-311.pyc +0 -0
  15. src/core/containers/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  16. src/core/containers/runtime/__pycache__/providers.cpython-311.pyc +0 -0
  17. src/core/containers/runtime/__pycache__/providers.cpython-313.pyc +0 -0
  18. src/core/containers/runtime/providers.py +6 -2
  19. src/core/env_server/__pycache__/__init__.cpython-311.pyc +0 -0
  20. src/core/env_server/__pycache__/__init__.cpython-313.pyc +0 -0
  21. src/core/env_server/__pycache__/base_transforms.cpython-311.pyc +0 -0
  22. src/core/env_server/__pycache__/base_transforms.cpython-313.pyc +0 -0
  23. src/core/env_server/__pycache__/http_server.cpython-311.pyc +0 -0
  24. src/core/env_server/__pycache__/http_server.cpython-313.pyc +0 -0
  25. src/core/env_server/__pycache__/interfaces.cpython-311.pyc +0 -0
  26. src/core/env_server/__pycache__/interfaces.cpython-313.pyc +0 -0
  27. src/core/env_server/__pycache__/types.cpython-311.pyc +0 -0
  28. src/core/env_server/__pycache__/types.cpython-313.pyc +0 -0
  29. src/core/env_server/__pycache__/web_interface.cpython-311.pyc +0 -0
  30. src/core/env_server/__pycache__/web_interface.cpython-313.pyc +0 -0
  31. src/core/http_env_client.py +15 -5
  32. src/core/pyproject.toml +46 -0
  33. src/core/tools/__init__.py +6 -1
  34. src/core/tools/git_server_client.py +362 -0
  35. src/envs/echo_env/README.md +13 -0
  36. src/envs/echo_env/__pycache__/__init__.cpython-311.pyc +0 -0
  37. src/envs/echo_env/__pycache__/__init__.cpython-313.pyc +0 -0
  38. src/envs/echo_env/__pycache__/client.cpython-311.pyc +0 -0
  39. src/envs/echo_env/__pycache__/client.cpython-313.pyc +0 -0
  40. src/envs/echo_env/__pycache__/models.cpython-311.pyc +0 -0
  41. src/envs/echo_env/client.py +2 -1
  42. src/envs/echo_env/server/__pycache__/__init__.cpython-311.pyc +0 -0
  43. src/envs/echo_env/server/__pycache__/app.cpython-311.pyc +0 -0
  44. src/envs/echo_env/server/__pycache__/echo_environment.cpython-311.pyc +0 -0
Dockerfile CHANGED
@@ -4,17 +4,24 @@
4
  # This source code is licensed under the BSD-style license found in the
5
  # LICENSE file in the root directory of this source tree.
6
 
7
- # Use the specified openenv-base image
8
- FROM ghcr.io/meta-pytorch/openenv-base:sha-7dd8148
 
 
 
9
 
10
  # Copy only what's needed for this environment
11
  COPY src/core/ /app/src/core/
12
  COPY src/envs/echo_env/ /app/src/envs/echo_env/
13
 
 
 
 
14
  # Health check
15
  HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
16
  CMD curl -f http://localhost:8000/health || exit 1
17
 
18
  # Run the FastAPI server
19
- CMD ["uvicorn", "envs.echo_env.server.app:app", "--host", "0.0.0.0", "--port", "8000"]
20
  ENV ENABLE_WEB_INTERFACE=true
 
 
 
4
  # This source code is licensed under the BSD-style license found in the
5
  # LICENSE file in the root directory of this source tree.
6
 
7
+ # Use the standard openenv base image
8
+ # Built from: docker build -t openenv-base:latest -f src/core/containers/images/Dockerfile .
9
+ # In GitHub Actions, this is overridden to use the GHCR base image
10
+ ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
11
+ FROM ghcr.io/meta-pytorch/openenv-base:latest
12
 
13
  # Copy only what's needed for this environment
14
  COPY src/core/ /app/src/core/
15
  COPY src/envs/echo_env/ /app/src/envs/echo_env/
16
 
17
+ # Copy README for web interface documentation
18
+ COPY src/envs/echo_env/README.md /app/README.md
19
+
20
  # Health check
21
  HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
22
  CMD curl -f http://localhost:8000/health || exit 1
23
 
24
  # Run the FastAPI server
 
25
  ENV ENABLE_WEB_INTERFACE=true
26
+
27
+ CMD ["uvicorn", "envs.echo_env.server.app:app", "--host", "0.0.0.0", "--port", "8000"]
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: echo_env
3
  emoji: 🔊
4
- colorFrom: green
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  app_port: 8000
@@ -11,40 +11,136 @@ tags:
11
  - openenv
12
  ---
13
 
14
- # Echo_env Environment Server
15
 
16
- FastAPI server for echo_env environment powered by Meta's OpenEnv.
17
 
18
- ## About
19
 
20
- This Space provides a containerized environment for echo_env interactions.
21
- Built with FastAPI and OpenEnv framework.
22
 
23
- ## Web Interface
 
24
 
25
- This deployment includes an interactive web interface for exploring the environment:
26
- - **HumanAgent Interface**: Interact with the environment using a web form
27
- - **State Observer**: Real-time view of environment state and action history
28
- - **Live Updates**: WebSocket-based real-time updates
29
 
30
- Access the web interface at: `/web`
 
 
31
 
32
- ## Echo Environment
 
33
 
34
- Simple test environment that echoes back messages. Perfect for testing the OpenEnv APIs.
 
 
 
 
 
35
 
36
- ### Usage
37
- Send a POST request to `/step` with:
38
- ```json
39
- {
40
- "message": "Hello World"
41
- }
42
  ```
43
 
44
- ## API Documentation
 
 
 
 
45
 
46
- Visit `/docs` for interactive API documentation.
47
 
48
- ## Health Check
49
 
50
- The environment provides a health check endpoint at `/health`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Echo Environment Server
3
  emoji: 🔊
4
+ colorFrom: blue
5
+ colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
  app_port: 8000
 
11
  - openenv
12
  ---
13
 
14
+ # Echo Environment
15
 
16
+ A simple test environment that echoes back messages. Perfect for testing the env APIs as well as demonstrating environment usage patterns.
17
 
18
+ ## Quick Start
19
 
20
+ The simplest way to use the Echo environment is through the `EchoEnv` class:
 
21
 
22
+ ```python
23
+ from envs.echo_env import EchoAction, EchoEnv
24
 
25
+ try:
26
+ # Create environment from Docker image
27
+ echo_env = EchoEnv.from_docker_image("echo-env:latest")
 
28
 
29
+ # Reset
30
+ result = echo_env.reset()
31
+ print(f"Reset: {result.observation.echoed_message}")
32
 
33
+ # Send multiple messages
34
+ messages = ["Hello, World!", "Testing echo", "Final message"]
35
 
36
+ for msg in messages:
37
+ result = echo_env.step(EchoAction(message=msg))
38
+ print(f"Sent: '{msg}'")
39
+ print(f" → Echoed: '{result.observation.echoed_message}'")
40
+ print(f" → Length: {result.observation.message_length}")
41
+ print(f" → Reward: {result.reward}")
42
 
43
+ finally:
44
+ # Always clean up
45
+ echo_env.close()
 
 
 
46
  ```
47
 
48
+ That's it! The `EchoEnv.from_docker_image()` method handles:
49
+ - Starting the Docker container
50
+ - Waiting for the server to be ready
51
+ - Connecting to the environment
52
+ - Container cleanup when you call `close()`
53
 
54
+ ## Building the Docker Image
55
 
56
+ Before using the environment, you need to build the Docker image:
57
 
58
+ ```bash
59
+ # From project root
60
+ docker build -t echo-env:latest -f src/envs/echo_env/server/Dockerfile .
61
+ ```
62
+
63
+ ## Environment Details
64
+
65
+ ### Action
66
+ **EchoAction**: Contains a single field
67
+ - `message` (str) - The message to echo back
68
+
69
+ ### Observation
70
+ **EchoObservation**: Contains the echo response and metadata
71
+ - `echoed_message` (str) - The message echoed back
72
+ - `message_length` (int) - Length of the message
73
+ - `reward` (float) - Reward based on message length (length × 0.1)
74
+ - `done` (bool) - Always False for echo environment
75
+ - `metadata` (dict) - Additional info like step count
76
+
77
+ ### Reward
78
+ The reward is calculated as: `message_length × 0.1`
79
+ - "Hi" → reward: 0.2
80
+ - "Hello, World!" → reward: 1.3
81
+ - Empty message → reward: 0.0
82
+
83
+ ## Advanced Usage
84
+
85
+ ### Connecting to an Existing Server
86
+
87
+ If you already have an Echo environment server running, you can connect directly:
88
+
89
+ ```python
90
+ from envs.echo_env import EchoEnv
91
+
92
+ # Connect to existing server
93
+ echo_env = EchoEnv(base_url="<ENV_HTTP_URL_HERE>")
94
+
95
+ # Use as normal
96
+ result = echo_env.reset()
97
+ result = echo_env.step(EchoAction(message="Hello!"))
98
+ ```
99
+
100
+ Note: When connecting to an existing server, `echo_env.close()` will NOT stop the server.
101
+
102
+ ## Development & Testing
103
+
104
+ ### Direct Environment Testing
105
+
106
+ Test the environment logic directly without starting the HTTP server:
107
+
108
+ ```bash
109
+ # From the server directory
110
+ python3 src/envs/echo_env/server/test_echo_env.py
111
+ ```
112
+
113
+ This verifies that:
114
+ - Environment resets correctly
115
+ - Step executes actions properly
116
+ - State tracking works
117
+ - Rewards are calculated correctly
118
+
119
+ ### Running the Full Example
120
+
121
+ Run the complete example that demonstrates the full workflow:
122
+
123
+ ```bash
124
+ python3 examples/local_echo_env.py
125
+ ```
126
+
127
+ This example shows:
128
+ - Creating an environment from a Docker image
129
+ - Resetting and stepping through the environment
130
+ - Automatic cleanup with `close()`
131
+
132
+ ## Project Structure
133
+
134
+ ```
135
+ echo_env/
136
+ ├── __init__.py # Module exports
137
+ ├── README.md # This file
138
+ ├── client.py # EchoEnv client implementation
139
+ ├── models.py # Action and Observation models
140
+ └── server/
141
+ ├── __init__.py # Server module exports
142
+ ├── echo_environment.py # Core environment logic
143
+ ├── app.py # FastAPI application
144
+ ├── test_echo_env.py # Direct environment tests
145
+ └── Dockerfile # Container image definition
146
+ ```
src/core/README.md ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # <img width="35" height="35" alt="image" src="https://github.com/user-attachments/assets/2700a971-e5d6-4036-b03f-2f89c9791609" /> OpenEnv: Agentic Execution Environments
2
+
3
+ An e2e framework for creating, deploying and using isolated execution environments for agentic RL training, built using Gymnasium style simple APIs. OpenEnv provides a standard for interacting with agentic execution environments via simple Gymnasium style APIs - step(), reset(), state(). Users of agentic execution environments can interact with the environment during RL training loops using these simple APIs.
4
+
5
+ In addition to making it easier for researchers and RL framework writers, we also provide tools for environment creators making it easier for them to create richer environments and make them available over familar protocols like HTTP and packaged using canonical technologies like docker. Environment creators can use the OpenEnv framework to create environments that are isolated, secure, and easy to deploy and use.
6
+
7
+
8
+ ## Overview
9
+ `openenv-core` provides the foundational building blocks for creating and interacting with containerized environments over HTTP. It enables you to build agent environments that can be deployed as Docker containers and accessed via a simple HTTP API.
10
+
11
+ > ⚠️ **Early Development Warning** OpenEnv is currently in an experimental
12
+ > stage. You should expect bugs, incomplete features, and APIs that may change
13
+ > in future versions. The project welcomes bugfixes, but to make sure things are
14
+ > well coordinated you should discuss any significant change before starting the
15
+ > work. It's recommended that you signal your intention to contribute in the
16
+ > issue tracker, either by filing a new issue or by claiming an existing one.
17
+
18
+
19
+ # OpenEnv Core
20
+
21
+ Core components for OpenEnv - a framework for building HTTP-based agentic environments.
22
+
23
+ ## Features
24
+
25
+ - **HTTPEnvClient**: Generic HTTP client for interacting with remote environments
26
+ - **HTTPEnvServer**: FastAPI-based server wrapper for exposing environments over HTTP
27
+ - **Container Providers**: Pluggable architecture for running containers (Docker, Kubernetes, etc.)
28
+ - **Type System**: Strongly-typed Action/Observation/State interfaces
29
+ - **Web Interface**: Optional web UI for interacting with environments
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install openenv-core
35
+ ```
36
+
37
+ For development:
38
+ ```bash
39
+ pip install openenv-core[dev]
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ### Creating an Environment Client
45
+
46
+ ```python
47
+ from openenv_core import HTTPEnvClient, StepResult
48
+ from dataclasses import dataclass
49
+
50
+ @dataclass
51
+ class MyAction:
52
+ text: str
53
+
54
+ @dataclass
55
+ class MyObservation:
56
+ response: str
57
+
58
+ class MyEnvClient(HTTPEnvClient[MyAction, MyObservation]):
59
+ def _step_payload(self, action: MyAction) -> dict:
60
+ return {"text": action.text}
61
+
62
+ def _parse_result(self, payload: dict) -> StepResult[MyObservation]:
63
+ obs_data = payload["observation"]
64
+ return StepResult(
65
+ observation=MyObservation(**obs_data),
66
+ reward=payload.get("reward"),
67
+ done=payload.get("done", False)
68
+ )
69
+
70
+ def _parse_state(self, payload: dict) -> Any:
71
+ return payload
72
+
73
+ # Use with Docker
74
+ env = MyEnvClient.from_docker_image("my-env:latest")
75
+ result = env.reset()
76
+ step_result = env.step(MyAction(text="hello"))
77
+ env.close()
78
+ ```
79
+
80
+ ### Creating an Environment Server
81
+
82
+ ```python
83
+ from openenv_core.env_server import Environment, HTTPEnvServer, create_app
84
+ from dataclasses import dataclass
85
+
86
+ @dataclass
87
+ class MyAction:
88
+ text: str
89
+
90
+ @dataclass
91
+ class MyObservation:
92
+ response: str
93
+ reward: float = 0.0
94
+ done: bool = False
95
+
96
+ class MyEnvironment(Environment):
97
+ def reset(self) -> MyObservation:
98
+ return MyObservation(response="Ready")
99
+
100
+ def step(self, action: MyAction) -> MyObservation:
101
+ return MyObservation(
102
+ response=f"Echo: {action.text}",
103
+ reward=1.0,
104
+ done=False
105
+ )
106
+
107
+ # Create FastAPI app
108
+ env = MyEnvironment()
109
+ app = create_app(env, MyAction, MyObservation)
110
+
111
+ # Run with: uvicorn module:app --host 0.0.0.0 --port 8000
112
+ ```
113
+
114
+ ## Container Providers
115
+
116
+ OpenEnv Core supports multiple container providers:
117
+
118
+ ### Local Docker Provider
119
+
120
+ ```python
121
+ from openenv_core.containers.runtime import LocalDockerProvider
122
+
123
+ provider = LocalDockerProvider()
124
+ base_url = provider.start_container("my-env:latest")
125
+ provider.wait_for_ready(base_url)
126
+ # Use environment...
127
+ provider.stop_container()
128
+ ```
129
+
130
+ ### Kubernetes Provider (Coming Soon)
131
+
132
+ ```python
133
+ from openenv_core.containers.runtime import KubernetesProvider
134
+
135
+ provider = KubernetesProvider(namespace="envs")
136
+ base_url = provider.start_container("my-env:latest")
137
+ # Use environment...
138
+ provider.stop_container()
139
+ ```
140
+
141
+
142
+ ## API Reference
143
+
144
+ ### HTTPEnvClient
145
+
146
+ Base class for environment clients with these abstract methods:
147
+
148
+ - `_step_payload(action)`: Convert action to JSON
149
+ - `_parse_result(payload)`: Parse response to StepResult
150
+ - `_parse_state(payload)`: Parse state response
151
+
152
+ ### HTTPEnvServer
153
+
154
+ Server wrapper with these methods:
155
+
156
+ - `register_routes(app)`: Register endpoints on FastAPI app
157
+ - `_deserialize_action(data)`: Convert JSON to Action
158
+ - `_serialize_observation(obs)`: Convert Observation to JSON
159
+
160
+ ### Environment Interface
161
+
162
+ Base interface for environment implementations:
163
+
164
+ - `reset()`: Reset environment and return initial observation
165
+ - `step(action)`: Execute action and return observation
166
+ - `state`: Property returning current environment state
167
+
168
+ ## License
169
+
170
+ This project is licensed under the BSD-3-Clause License - see the LICENSE file for details.
171
+
172
+ ## Contributing
173
+
174
+ Contributions are welcome! Please see the main OpenEnv repository for contribution guidelines.
175
+
176
+ ## Links
177
+
178
+ - **Homepage**: https://github.com/facebookresearch/OpenEnv
179
+ - **Documentation**: https://github.com/facebookresearch/OpenEnv/blob/main/README.md
180
+ - **Bug Tracker**: https://github.com/facebookresearch/OpenEnv/issues
src/core/__init__.py CHANGED
@@ -8,8 +8,8 @@
8
 
9
  # Re-export main components from submodules for convenience
10
  from .env_server import *
 
11
  from .http_env_client import HTTPEnvClient
12
- from .types import StepResult
13
 
14
  # Note: MCP module doesn't export anything yet
15
 
 
8
 
9
  # Re-export main components from submodules for convenience
10
  from .env_server import *
11
+ from .client_types import StepResult
12
  from .http_env_client import HTTPEnvClient
 
13
 
14
  # Note: MCP module doesn't export anything yet
15
 
src/core/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (400 Bytes). View file
 
src/core/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (383 Bytes). View file
 
src/core/__pycache__/http_env_client.cpython-311.pyc ADDED
Binary file (7.68 kB). View file
 
src/core/__pycache__/http_env_client.cpython-313.pyc ADDED
Binary file (6.93 kB). View file
 
src/core/__pycache__/types.cpython-311.pyc ADDED
Binary file (1.09 kB). View file
 
src/core/__pycache__/types.cpython-313.pyc ADDED
Binary file (993 Bytes). View file
 
src/core/client_types.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Type definitions for EnvTorch
2
+ from dataclasses import dataclass
3
+ from typing import Any, Generic, Optional, TypeVar
4
+
5
+ # Generic type for observations
6
+ ObsT = TypeVar("ObsT") # TypeVar for typehinting in IDEs
7
+
8
+
9
+ @dataclass
10
+ class StepResult(Generic[ObsT]):
11
+ """
12
+ Represents the result of one environment step.
13
+
14
+ Attributes:
15
+ observation: The environment's observation after the action.
16
+ reward: Scalar reward for this step (optional).
17
+ done: Whether the episode is finished.
18
+ """
19
+
20
+ observation: ObsT
21
+ reward: Optional[float] = None
22
+ done: bool = False
src/core/containers/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (206 Bytes). View file
 
src/core/containers/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (224 Bytes). View file
 
src/core/containers/runtime/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (389 Bytes). View file
 
src/core/containers/runtime/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (375 Bytes). View file
 
src/core/containers/runtime/__pycache__/providers.cpython-311.pyc ADDED
Binary file (10.9 kB). View file
 
src/core/containers/runtime/__pycache__/providers.cpython-313.pyc ADDED
Binary file (9.64 kB). View file
 
src/core/containers/runtime/providers.py CHANGED
@@ -169,8 +169,12 @@ class LocalDockerProvider(ContainerProvider):
169
  cmd.append(image)
170
 
171
  # Run container
172
- result = subprocess.run(cmd, capture_output=True, text=True, check=True)
173
- self._container_id = result.stdout.strip()
 
 
 
 
174
 
175
  # Wait a moment for container to start
176
  time.sleep(1)
 
169
  cmd.append(image)
170
 
171
  # Run container
172
+ try:
173
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
174
+ self._container_id = result.stdout.strip()
175
+ except subprocess.CalledProcessError as e:
176
+ error_msg = f"Failed to start Docker container.\nCommand: {' '.join(cmd)}\nExit code: {e.returncode}\nStderr: {e.stderr}\nStdout: {e.stdout}"
177
+ raise RuntimeError(error_msg) from e
178
 
179
  # Wait a moment for container to start
180
  time.sleep(1)
src/core/env_server/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (898 Bytes). View file
 
src/core/env_server/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (788 Bytes). View file
 
src/core/env_server/__pycache__/base_transforms.cpython-311.pyc ADDED
Binary file (1.67 kB). View file
 
src/core/env_server/__pycache__/base_transforms.cpython-313.pyc ADDED
Binary file (1.57 kB). View file
 
src/core/env_server/__pycache__/http_server.cpython-311.pyc ADDED
Binary file (9.2 kB). View file
 
src/core/env_server/__pycache__/http_server.cpython-313.pyc ADDED
Binary file (8.33 kB). View file
 
src/core/env_server/__pycache__/interfaces.cpython-311.pyc ADDED
Binary file (5.22 kB). View file
 
src/core/env_server/__pycache__/interfaces.cpython-313.pyc ADDED
Binary file (4.68 kB). View file
 
src/core/env_server/__pycache__/types.cpython-311.pyc ADDED
Binary file (2.39 kB). View file
 
src/core/env_server/__pycache__/types.cpython-313.pyc ADDED
Binary file (2.66 kB). View file
 
src/core/env_server/__pycache__/web_interface.cpython-311.pyc ADDED
Binary file (29.9 kB). View file
 
src/core/env_server/__pycache__/web_interface.cpython-313.pyc ADDED
Binary file (59.3 kB). View file
 
src/core/http_env_client.py CHANGED
@@ -12,11 +12,12 @@ Future hooks (commented below) for:
12
  from __future__ import annotations
13
 
14
  from abc import ABC, abstractmethod
15
- from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Type, TypeVar
16
- from .containers.runtime import LocalDockerProvider
17
  import requests
18
 
19
- from .types import StepResult
 
20
 
21
  if TYPE_CHECKING:
22
  from .containers.runtime import ContainerProvider
@@ -45,6 +46,7 @@ class HTTPEnvClient(ABC, Generic[ActT, ObsT]):
45
  cls: Type[EnvClientT],
46
  image: str,
47
  provider: Optional["ContainerProvider"] = None,
 
48
  ) -> EnvClientT:
49
  """
50
  Create an environment client by spinning up a Docker container locally.
@@ -60,6 +62,8 @@ class HTTPEnvClient(ABC, Generic[ActT, ObsT]):
60
  Args:
61
  image: Docker image name to run (e.g., "echo-env:latest")
62
  provider: Container provider to use (defaults to LocalDockerProvider)
 
 
63
 
64
  Returns:
65
  An instance of the client class connected to the running container
@@ -71,6 +75,12 @@ class HTTPEnvClient(ABC, Generic[ActT, ObsT]):
71
  >>> # Create environment from image
72
  >>> env = CodingEnv.from_docker_image("coding-env:latest")
73
  >>>
 
 
 
 
 
 
74
  >>> # Use the environment
75
  >>> result = env.reset()
76
  >>> print(result.observation)
@@ -86,8 +96,8 @@ class HTTPEnvClient(ABC, Generic[ActT, ObsT]):
86
  if provider is None:
87
  provider = LocalDockerProvider()
88
 
89
- # 1. Start container
90
- base_url = provider.start_container(image)
91
 
92
  # 2. Wait for server to be ready
93
  provider.wait_for_ready(base_url)
 
12
  from __future__ import annotations
13
 
14
  from abc import ABC, abstractmethod
15
+ from typing import Any, Dict, Generic, Optional, Type, TYPE_CHECKING, TypeVar
16
+
17
  import requests
18
 
19
+ from .client_types import StepResult
20
+ from .containers.runtime import LocalDockerProvider
21
 
22
  if TYPE_CHECKING:
23
  from .containers.runtime import ContainerProvider
 
46
  cls: Type[EnvClientT],
47
  image: str,
48
  provider: Optional["ContainerProvider"] = None,
49
+ **kwargs: Any,
50
  ) -> EnvClientT:
51
  """
52
  Create an environment client by spinning up a Docker container locally.
 
62
  Args:
63
  image: Docker image name to run (e.g., "echo-env:latest")
64
  provider: Container provider to use (defaults to LocalDockerProvider)
65
+ **kwargs: Additional arguments to pass to provider.start_container()
66
+ (e.g., env_vars, port)
67
 
68
  Returns:
69
  An instance of the client class connected to the running container
 
75
  >>> # Create environment from image
76
  >>> env = CodingEnv.from_docker_image("coding-env:latest")
77
  >>>
78
+ >>> # Create environment with custom env vars
79
+ >>> env = CodingEnv.from_docker_image(
80
+ ... "coding-env:latest",
81
+ ... env_vars={"MY_VAR": "value"}
82
+ ... )
83
+ >>>
84
  >>> # Use the environment
85
  >>> result = env.reset()
86
  >>> print(result.observation)
 
96
  if provider is None:
97
  provider = LocalDockerProvider()
98
 
99
+ # 1. Start container with optional kwargs (e.g., env_vars, port)
100
+ base_url = provider.start_container(image, **kwargs)
101
 
102
  # 2. Wait for server to be ready
103
  provider.wait_for_ready(base_url)
src/core/pyproject.toml ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "openenv-core"
7
+ version = "0.1.0"
8
+ description = "Core components for OpenEnv - HTTP-based agentic environments"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "BSD-3-Clause"}
12
+ authors = [
13
+ {name = "Meta Platforms, Inc.", email = "opensource@meta.com"}
14
+ ]
15
+ keywords = ["environment", "agent", "http", "docker", "fastapi"]
16
+
17
+ dependencies = [
18
+ "requests>=2.25.0",
19
+ "fastapi>=0.104.0",
20
+ "uvicorn>=0.24.0",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "pytest>=7.0.0",
26
+ "black>=23.0.0",
27
+ "ruff>=0.1.0",
28
+ "mypy>=1.0.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/facebookresearch/OpenEnv"
33
+ Repository = "https://github.com/facebookresearch/OpenEnv"
34
+ Documentation = "https://github.com/facebookresearch/OpenEnv/blob/main/README.md"
35
+ "Bug Tracker" = "https://github.com/facebookresearch/OpenEnv/issues"
36
+
37
+ [tool.setuptools]
38
+ py-modules = ["openenv_core.__init__", "openenv_core.http_env_client", "openenv_core.client_types"]
39
+ packages = [
40
+ "openenv_core",
41
+ "openenv_core.containers",
42
+ "openenv_core.containers.runtime",
43
+ "openenv_core.env_server",
44
+ "openenv_core.tools"
45
+ ]
46
+ package-dir = {"openenv_core" = "."}
src/core/tools/__init__.py CHANGED
@@ -6,6 +6,11 @@
6
 
7
  """Core tools for code execution and other utilities."""
8
 
 
9
  from .local_python_executor import PyExecutor
10
 
11
- __all__ = ["PyExecutor"]
 
 
 
 
 
6
 
7
  """Core tools for code execution and other utilities."""
8
 
9
+ from .git_server_client import GitServerClient, RepoInfo
10
  from .local_python_executor import PyExecutor
11
 
12
+ __all__ = [
13
+ "PyExecutor",
14
+ "GitServerClient",
15
+ "RepoInfo",
16
+ ]
src/core/tools/git_server_client.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Git Server Client for connecting to external Gitea instance.
4
+
5
+ This module provides a lightweight client for interacting with a shared
6
+ Gitea service, optimized for task-based isolation where multiple environment
7
+ instances share the same Gitea server but have isolated workspaces.
8
+ """
9
+
10
+ import json
11
+ import os
12
+ import shutil
13
+ import subprocess
14
+ import time
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from urllib.parse import urlparse
18
+
19
+
20
+ @dataclass
21
+ class RepoInfo:
22
+ """Information about a repository."""
23
+
24
+ name: str
25
+ url: str
26
+ commit: str
27
+ clone_url: str
28
+
29
+
30
+ class GitServerClient:
31
+ """
32
+ Client for connecting to an external Gitea server.
33
+
34
+ This client is optimized for task-based isolation where:
35
+ - Multiple tasks share the same Gitea instance
36
+ - Each task has its own isolated workspace
37
+ - Fast reset() via git operations (no server restart)
38
+ - Repos are pre-migrated to Gitea once
39
+
40
+ Args:
41
+ gitea_url: URL of the Gitea server (e.g., "http://gitea:3000")
42
+ username: Gitea username for authentication
43
+ password: Gitea password for authentication
44
+ workspace_dir: Local workspace directory for cloning repos
45
+
46
+ Example:
47
+ >>> # Connect to shared Gitea (credentials from environment)
48
+ >>> import os
49
+ >>> client = GitServerClient(
50
+ ... gitea_url=os.getenv("GITEA_URL"),
51
+ ... username=os.getenv("GITEA_USERNAME"),
52
+ ... password=os.getenv("GITEA_PASSWORD")
53
+ ... )
54
+ >>> client.wait_for_ready()
55
+ >>> # Clone repo to workspace
56
+ >>> path = client.clone_to_workspace("my-repo", commit="abc123")
57
+ >>> # Fast reset to base state
58
+ >>> client.reset_workspace("my-repo", commit="abc123")
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ gitea_url: str,
64
+ username: str,
65
+ password: str,
66
+ workspace_dir: str = "/workspace",
67
+ ):
68
+ """Initialize Git Server Client."""
69
+ self.gitea_url = gitea_url.rstrip("/")
70
+ self.username = username
71
+ self.password = password
72
+ self.workspace_dir = Path(workspace_dir)
73
+ self.is_ready = False
74
+
75
+ # Parse Gitea URL
76
+ parsed = urlparse(self.gitea_url)
77
+ self.domain = parsed.hostname or "localhost"
78
+ self.port = parsed.port or 3000
79
+
80
+ # Ensure workspace exists
81
+ os.makedirs(self.workspace_dir, exist_ok=True)
82
+
83
+ # Configure git credentials
84
+ self._configure_git()
85
+
86
+ def _configure_git(self):
87
+ """Configure git credentials for automatic authentication."""
88
+ home_dir = Path.home()
89
+
90
+ # Git config
91
+ git_config = f"""[user]
92
+ name = {self.username}
93
+ email = {self.username}@local.env
94
+ [init]
95
+ defaultBranch = main
96
+ [credential]
97
+ helper = store
98
+ """
99
+ gitconfig_path = home_dir / ".gitconfig"
100
+ gitconfig_path.write_text(git_config)
101
+
102
+ # Git credentials
103
+ git_credentials = f"http://{self.username}:{self.password}@{self.domain}:{self.port}\n"
104
+ gitcreds_path = home_dir / ".git-credentials"
105
+ gitcreds_path.write_text(git_credentials)
106
+ gitcreds_path.chmod(0o600)
107
+
108
+ def wait_for_ready(self, timeout: int = 30) -> bool:
109
+ """
110
+ Wait for Gitea server to be ready.
111
+
112
+ Args:
113
+ timeout: Maximum seconds to wait
114
+
115
+ Returns:
116
+ True if server is ready, False otherwise
117
+ """
118
+ start_time = time.time()
119
+ while time.time() - start_time < timeout:
120
+ try:
121
+ result = subprocess.run(
122
+ ["curl", "-sf", f"{self.gitea_url}/"],
123
+ capture_output=True,
124
+ timeout=5,
125
+ )
126
+ if result.returncode == 0:
127
+ self.is_ready = True
128
+ return True
129
+ except subprocess.TimeoutExpired:
130
+ pass
131
+ except Exception:
132
+ pass
133
+
134
+ time.sleep(1)
135
+
136
+ return False
137
+
138
+ def list_repositories(self) -> list[dict[str, str]]:
139
+ """
140
+ List all repositories in Gitea.
141
+
142
+ Returns:
143
+ List of repository information dictionaries
144
+ """
145
+ if not self.is_ready:
146
+ raise RuntimeError("Gitea server is not ready")
147
+
148
+ result = subprocess.run(
149
+ [
150
+ "curl",
151
+ "-s",
152
+ f"{self.gitea_url}/api/v1/user/repos",
153
+ "-u",
154
+ f"{self.username}:{self.password}",
155
+ ],
156
+ capture_output=True,
157
+ text=True,
158
+ )
159
+
160
+ if result.returncode != 0:
161
+ return []
162
+
163
+ try:
164
+ repos = json.loads(result.stdout)
165
+ return [
166
+ {
167
+ "name": repo["name"],
168
+ "full_name": repo["full_name"],
169
+ "clone_url": repo["clone_url"],
170
+ "description": repo.get("description", ""),
171
+ }
172
+ for repo in repos
173
+ ]
174
+ except (json.JSONDecodeError, KeyError):
175
+ return []
176
+
177
+ def clone_to_workspace(
178
+ self, repo_name: str, target_dir: str | None = None, commit: str = "main"
179
+ ) -> str:
180
+ """
181
+ Clone a repository to the workspace at a specific commit.
182
+
183
+ This creates a fresh clone optimized for task isolation.
184
+
185
+ Args:
186
+ repo_name: Name of repository to clone
187
+ target_dir: Target directory name (defaults to repo_name)
188
+ commit: Commit hash or branch to checkout
189
+
190
+ Returns:
191
+ Path to cloned repository
192
+
193
+ Raises:
194
+ RuntimeError: If clone fails
195
+ """
196
+ if not self.is_ready:
197
+ raise RuntimeError("Gitea server is not ready")
198
+
199
+ target_dir = target_dir or repo_name
200
+ target_path = self.workspace_dir / target_dir
201
+
202
+ # Remove existing directory if present
203
+ if target_path.exists():
204
+ shutil.rmtree(target_path)
205
+
206
+ clone_url = f"{self.gitea_url}/{self.username}/{repo_name}.git"
207
+
208
+ # Clone repository
209
+ result = subprocess.run(
210
+ ["git", "clone", clone_url, str(target_path)],
211
+ capture_output=True,
212
+ text=True,
213
+ )
214
+
215
+ if result.returncode != 0:
216
+ raise RuntimeError(f"Clone failed: {result.stderr}")
217
+
218
+ # Checkout specific commit
219
+ if commit != "main":
220
+ result = subprocess.run(
221
+ ["git", "checkout", commit],
222
+ cwd=str(target_path),
223
+ capture_output=True,
224
+ text=True,
225
+ )
226
+
227
+ if result.returncode != 0:
228
+ raise RuntimeError(f"Checkout failed: {result.stderr}")
229
+
230
+ return str(target_path)
231
+
232
+ def reset_workspace(self, repo_name: str, commit: str = "main") -> bool:
233
+ """
234
+ Fast reset of workspace to base state (optimized for task resets).
235
+
236
+ This is much faster than re-cloning. It:
237
+ 1. Checks out the target commit
238
+ 2. Resets to that commit (hard)
239
+ 3. Cleans untracked files
240
+
241
+ Args:
242
+ repo_name: Name of repository (directory in workspace)
243
+ commit: Commit hash or branch to reset to
244
+
245
+ Returns:
246
+ True if reset successful
247
+
248
+ Raises:
249
+ RuntimeError: If reset fails
250
+ """
251
+ repo_path = self.workspace_dir / repo_name
252
+
253
+ if not repo_path.exists():
254
+ raise RuntimeError(f"Repository not found in workspace: {repo_name}")
255
+
256
+ # Fetch latest (in case commit is new)
257
+ subprocess.run(
258
+ ["git", "fetch", "--all"],
259
+ cwd=str(repo_path),
260
+ capture_output=True,
261
+ )
262
+
263
+ # Checkout and hard reset to commit
264
+ result = subprocess.run(
265
+ ["git", "checkout", commit],
266
+ cwd=str(repo_path),
267
+ capture_output=True,
268
+ text=True,
269
+ )
270
+
271
+ if result.returncode != 0:
272
+ raise RuntimeError(f"Checkout failed: {result.stderr}")
273
+
274
+ result = subprocess.run(
275
+ ["git", "reset", "--hard", f"origin/{commit}" if commit != "main" else commit],
276
+ cwd=str(repo_path),
277
+ capture_output=True,
278
+ text=True,
279
+ )
280
+
281
+ if result.returncode != 0:
282
+ # Try without origin/ prefix
283
+ result = subprocess.run(
284
+ ["git", "reset", "--hard", commit],
285
+ cwd=str(repo_path),
286
+ capture_output=True,
287
+ text=True,
288
+ )
289
+ if result.returncode != 0:
290
+ raise RuntimeError(f"Reset failed: {result.stderr}")
291
+
292
+ # Clean untracked files and directories
293
+ subprocess.run(
294
+ ["git", "clean", "-fdx"],
295
+ cwd=str(repo_path),
296
+ capture_output=True,
297
+ )
298
+
299
+ return True
300
+
301
+ def execute_git_command(
302
+ self, command: str, working_dir: str = ""
303
+ ) -> tuple[int, str, str]:
304
+ """
305
+ Execute a git command in the workspace.
306
+
307
+ Args:
308
+ command: Git command to execute (without 'git' prefix)
309
+ working_dir: Working directory relative to workspace
310
+
311
+ Returns:
312
+ Tuple of (exit_code, stdout, stderr)
313
+ """
314
+ work_path = (
315
+ self.workspace_dir / working_dir if working_dir else self.workspace_dir
316
+ )
317
+
318
+ if not work_path.exists():
319
+ return (1, "", f"Working directory does not exist: {work_path}")
320
+
321
+ # Split command safely
322
+ cmd_parts = ["git"] + command.split()
323
+
324
+ result = subprocess.run(
325
+ cmd_parts,
326
+ cwd=str(work_path),
327
+ capture_output=True,
328
+ text=True,
329
+ )
330
+
331
+ return (result.returncode, result.stdout, result.stderr)
332
+
333
+ def get_current_commit(self, repo_name: str) -> str:
334
+ """
335
+ Get current commit hash of a workspace repository.
336
+
337
+ Args:
338
+ repo_name: Name of repository in workspace
339
+
340
+ Returns:
341
+ Commit hash
342
+ """
343
+ repo_path = self.workspace_dir / repo_name
344
+
345
+ if not repo_path.exists():
346
+ raise RuntimeError(f"Repository not found: {repo_name}")
347
+
348
+ result = subprocess.run(
349
+ ["git", "rev-parse", "HEAD"],
350
+ cwd=str(repo_path),
351
+ capture_output=True,
352
+ text=True,
353
+ )
354
+
355
+ if result.returncode != 0:
356
+ raise RuntimeError(f"Failed to get commit: {result.stderr}")
357
+
358
+ return result.stdout.strip()
359
+
360
+ def workspace_exists(self, repo_name: str) -> bool:
361
+ """Check if a repository exists in workspace."""
362
+ return (self.workspace_dir / repo_name).exists()
src/envs/echo_env/README.md CHANGED
@@ -1,3 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Echo Environment
2
 
3
  A simple test environment that echoes back messages. Perfect for testing the env APIs as well as demonstrating environment usage patterns.
 
1
+ ---
2
+ title: Echo Environment Server
3
+ emoji: 🔊
4
+ colorFrom: blue
5
+ colorTo: yellow
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv
12
+ ---
13
+
14
  # Echo Environment
15
 
16
  A simple test environment that echoes back messages. Perfect for testing the env APIs as well as demonstrating environment usage patterns.
src/envs/echo_env/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (412 Bytes). View file
 
src/envs/echo_env/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (390 Bytes). View file
 
src/envs/echo_env/__pycache__/client.cpython-311.pyc ADDED
Binary file (3.96 kB). View file
 
src/envs/echo_env/__pycache__/client.cpython-313.pyc ADDED
Binary file (3.43 kB). View file
 
src/envs/echo_env/__pycache__/models.cpython-311.pyc ADDED
Binary file (1.34 kB). View file
 
src/envs/echo_env/client.py CHANGED
@@ -13,9 +13,10 @@ over HTTP.
13
 
14
  from typing import Any, Dict
15
 
 
 
16
  from core.env_server.types import State
17
  from core.http_env_client import HTTPEnvClient
18
- from core.types import StepResult
19
 
20
  from .models import EchoAction, EchoObservation
21
 
 
13
 
14
  from typing import Any, Dict
15
 
16
+ from core.client_types import StepResult
17
+
18
  from core.env_server.types import State
19
  from core.http_env_client import HTTPEnvClient
 
20
 
21
  from .models import EchoAction, EchoObservation
22
 
src/envs/echo_env/server/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (304 Bytes). View file
 
src/envs/echo_env/server/__pycache__/app.cpython-311.pyc ADDED
Binary file (1.16 kB). View file
 
src/envs/echo_env/server/__pycache__/echo_environment.cpython-311.pyc ADDED
Binary file (3.59 kB). View file