burtenshaw HF Staff commited on
Commit
2be4f6d
·
verified ·
1 Parent(s): 80de4c0

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +12 -0
  2. .gitignore +1 -0
  3. Dockerfile +128 -0
  4. README.md +595 -5
  5. __init__.py +12 -0
  6. assets/01-messages.mov +3 -0
  7. assets/02-editor.mov +3 -0
  8. assets/03-calendar.mov +3 -0
  9. assets/04-todo.mov +3 -0
  10. assets/OpenApps_OpenEnv_RL.png +3 -0
  11. assets/demo-showcase.html +624 -0
  12. assets/openapps-demo.gif +3 -0
  13. client.py +139 -0
  14. envs/openapp_env/.gitignore +1 -0
  15. envs/openapp_env/README.md +584 -0
  16. envs/openapp_env/__init__.py +12 -0
  17. envs/openapp_env/assets/01-messages.mov +3 -0
  18. envs/openapp_env/assets/02-editor.mov +3 -0
  19. envs/openapp_env/assets/03-calendar.mov +3 -0
  20. envs/openapp_env/assets/04-todo.mov +3 -0
  21. envs/openapp_env/assets/OpenApps_OpenEnv_RL.png +3 -0
  22. envs/openapp_env/assets/demo-showcase.html +624 -0
  23. envs/openapp_env/assets/openapps-demo.gif +3 -0
  24. envs/openapp_env/client.py +139 -0
  25. envs/openapp_env/example_usage.py +279 -0
  26. envs/openapp_env/models.py +86 -0
  27. envs/openapp_env/openenv.yaml +6 -0
  28. envs/openapp_env/pyproject.toml +58 -0
  29. envs/openapp_env/server/Dockerfile +128 -0
  30. envs/openapp_env/server/__init__.py +7 -0
  31. envs/openapp_env/server/app.py +59 -0
  32. envs/openapp_env/server/openapp_environment.py +659 -0
  33. envs/openapp_env/server/start.sh +44 -0
  34. envs/openapp_env/test_openapp_env.py +144 -0
  35. example_usage.py +279 -0
  36. models.py +86 -0
  37. openenv.yaml +6 -0
  38. pyproject.toml +58 -0
  39. server/Dockerfile +128 -0
  40. server/__init__.py +7 -0
  41. server/app.py +59 -0
  42. server/openapp_environment.py +659 -0
  43. server/start.sh +44 -0
  44. src/__init__.py +7 -0
  45. src/openenv.egg-info/PKG-INFO +337 -0
  46. src/openenv.egg-info/SOURCES.txt +142 -0
  47. src/openenv.egg-info/dependency_links.txt +1 -0
  48. src/openenv.egg-info/entry_points.txt +2 -0
  49. src/openenv.egg-info/requires.txt +32 -0
  50. src/openenv.egg-info/top_level.txt +2 -0
.gitattributes CHANGED
@@ -33,3 +33,15 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/01-messages.mov filter=lfs diff=lfs merge=lfs -text
37
+ assets/02-editor.mov filter=lfs diff=lfs merge=lfs -text
38
+ assets/03-calendar.mov filter=lfs diff=lfs merge=lfs -text
39
+ assets/04-todo.mov filter=lfs diff=lfs merge=lfs -text
40
+ assets/OpenApps_OpenEnv_RL.png filter=lfs diff=lfs merge=lfs -text
41
+ assets/openapps-demo.gif filter=lfs diff=lfs merge=lfs -text
42
+ envs/openapp_env/assets/01-messages.mov filter=lfs diff=lfs merge=lfs -text
43
+ envs/openapp_env/assets/02-editor.mov filter=lfs diff=lfs merge=lfs -text
44
+ envs/openapp_env/assets/03-calendar.mov filter=lfs diff=lfs merge=lfs -text
45
+ envs/openapp_env/assets/04-todo.mov filter=lfs diff=lfs merge=lfs -text
46
+ envs/openapp_env/assets/OpenApps_OpenEnv_RL.png filter=lfs diff=lfs merge=lfs -text
47
+ envs/openapp_env/assets/openapps-demo.gif filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ OpenApps
Dockerfile ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ # Dockerfile for OpenApp Environment
8
+ # This image provides OpenApps web application simulation for UI agent training
9
+ #
10
+ # This Dockerfile works for both local builds and HuggingFace Spaces deployment:
11
+ # - Local build: cd envs/openapp_env && docker build -t openapp-env:latest -f server/Dockerfile .
12
+ # - HuggingFace: Automatically deployed via `openenv push`
13
+ #
14
+ # Run with web interface:
15
+ # docker run -p 8000:8000 -e ENABLE_WEB_INTERFACE=true openapp-env:latest
16
+
17
+ FROM python:3.11-slim
18
+
19
+ # Set metadata
20
+ LABEL maintainer="OpenEnv Team"
21
+ LABEL description="OpenApp Environment with BrowserGym for UI agent training"
22
+ LABEL org.opencontainers.image.source="https://github.com/meta-pytorch/OpenEnv"
23
+
24
+ # Set working directory
25
+ WORKDIR /app/env
26
+
27
+ # Install system dependencies
28
+ # - git: required to clone OpenApps from GitHub
29
+ # - curl: for healthcheck
30
+ # - Playwright/BrowserGym dependencies: fonts, libraries for browser automation
31
+ RUN apt-get update && \
32
+ apt-get install -y --no-install-recommends \
33
+ git \
34
+ curl \
35
+ ca-certificates \
36
+ wget \
37
+ gnupg \
38
+ # Playwright/Chromium dependencies
39
+ libnss3 \
40
+ libnspr4 \
41
+ libatk1.0-0 \
42
+ libatk-bridge2.0-0 \
43
+ libcups2 \
44
+ libdrm2 \
45
+ libdbus-1-3 \
46
+ libxkbcommon0 \
47
+ libxcomposite1 \
48
+ libxdamage1 \
49
+ libxfixes3 \
50
+ libxrandr2 \
51
+ libgbm1 \
52
+ libasound2 \
53
+ libpango-1.0-0 \
54
+ libcairo2 \
55
+ libatspi2.0-0 \
56
+ libxshmfence1 \
57
+ fonts-liberation \
58
+ libappindicator3-1 \
59
+ xdg-utils && \
60
+ rm -rf /var/lib/apt/lists/*
61
+
62
+ # Set environment variables
63
+ ENV PYTHONUNBUFFERED=1
64
+
65
+ # Set working directory
66
+ WORKDIR /app/env
67
+
68
+ # Copy environment files
69
+ # Context is always the env directory (envs/openapp_env/)
70
+ # - GitHub Actions: uses context: envs/openapp_env
71
+ # - HuggingFace: openenv push uploads env dir as context
72
+ COPY . /app/env
73
+
74
+ # Install OpenApps FIRST to establish openai<2 (required by agentlab)
75
+ # This must happen before openenv-core to avoid version conflict
76
+ WORKDIR /app
77
+ RUN git clone https://github.com/facebookresearch/OpenApps.git openapps && \
78
+ cd openapps && \
79
+ pip install --no-cache-dir -e .
80
+
81
+ # Verify OpenApps installation
82
+ RUN python -c "import open_apps; print('✓ OpenApps installed')"
83
+
84
+ # Install openenv-core from GitHub with --no-deps to avoid openai>=2.7.2 conflict
85
+ # Then install only the server dependencies (no openai needed for server)
86
+ RUN pip install --no-cache-dir --no-deps "openenv-core[core]>=0.2.1" && \
87
+ pip install --no-cache-dir fastapi pydantic uvicorn requests websockets
88
+
89
+ # Install openapp_env and remaining dependencies
90
+ WORKDIR /app/env
91
+ RUN pip install --no-cache-dir -e .
92
+
93
+ # Verify installation
94
+ RUN python -c "import openapp_env; print('✓ openapp_env installed')" && \
95
+ python -c "import openapp_env.server.app; print('✓ openapp_env.server.app importable')"
96
+
97
+ # Install Playwright browsers (Chromium for BrowserGym)
98
+ # We already installed system dependencies above, so just install the browser
99
+ RUN playwright install chromium
100
+
101
+ # Copy startup script
102
+ WORKDIR /app/env
103
+ COPY server/start.sh /app/start.sh
104
+ RUN chmod +x /app/start.sh
105
+
106
+ # OpenApp-specific environment variables (can be overridden at runtime)
107
+ ENV OPENAPPS_URL=http://localhost:5001
108
+ ENV OPENAPPS_PORT=5001
109
+ ENV OPENAPP_HEADLESS=true
110
+ ENV OPENAPP_MAX_STEPS=50
111
+
112
+ # Hydra requires USER environment variable
113
+ ENV USER=root
114
+
115
+ # Enable web interface by default (set to false to disable)
116
+ ENV ENABLE_WEB_INTERFACE=true
117
+
118
+ # Expose ports (8000 for FastAPI, 5001 for OpenApps)
119
+ EXPOSE 8000 5001
120
+
121
+ # Health check
122
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
123
+ CMD curl -f http://localhost:8000/health || exit 1
124
+
125
+ # Run the startup script that launches both OpenApps server and FastAPI server
126
+ # Web interface will be available at /web if ENABLE_WEB_INTERFACE=true
127
+ # API documentation available at /docs
128
+ CMD ["/app/start.sh"]
README.md CHANGED
@@ -1,10 +1,600 @@
1
  ---
2
- title: Openapp Env-v2-1-0
3
- emoji: 👀
4
- colorFrom: green
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: OpenApp Environment Server
3
+ emoji: 🌐
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
  pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv
12
+ - OpenApps
13
+ - BrowserGym
14
+ - UI-Agents
15
+ - Reinforcement-Learning
16
  ---
17
 
18
+ ## Hugging Face Space Deployment
19
+
20
+ This Space is built from OpenEnv environment `openapp_env`.
21
+
22
+ - Space URL: `https://huggingface.co/spaces/openenv/openapp_env-v2-1-0`
23
+ - OpenEnv pinned ref: `v2.1.0`
24
+ - Hub tag: `openenv`
25
+
26
+ ### Connecting from Code
27
+
28
+ ```python
29
+ from envs.openapp_env import Env
30
+
31
+ env = Env(base_url="https://huggingface.co/spaces/openenv/openapp_env-v2-1-0")
32
+ ```
33
+
34
+ <!--
35
+ Copyright (c) Meta Platforms, Inc. and affiliates.
36
+ All rights reserved.
37
+
38
+ This source code is licensed under the BSD-style license found in the
39
+ LICENSE file in the root directory of this source tree.
40
+ -->
41
+
42
+ <div align="center">
43
+
44
+ # OpenApp Environment
45
+
46
+ <img src="assets/OpenApps_OpenEnv_RL.png" alt="OpenApps Environment" width="800"/>
47
+
48
+ *A web application simulation environment for OpenEnv that wraps the [OpenApps](https://github.com/facebookresearch/OpenApps) framework and BrowserGym.*
49
+
50
+ </div>
51
+
52
+ ## Overview
53
+
54
+ The OpenApp environment provides a simulated web application ecosystem where agents can interact with various apps (calendar, todo, messenger, maps) using browser-based actions.
55
+
56
+ <div align="center">
57
+ <img src="assets/openapps-demo.gif" alt="OpenApps Demo" width="800"/>
58
+ </div>
59
+
60
+ This environment is ideal for:
61
+
62
+ - Training and evaluating UI agents
63
+ - Testing web automation strategies
64
+ - Researching human-computer interaction
65
+ - Developing multimodal agents
66
+
67
+ ## Features
68
+
69
+ - **Multiple Apps**: Interact with calendar, todo list, messenger, and map applications
70
+ - **Browser-Based Actions**: Click, fill forms, navigate, scroll, and more
71
+ - **Task-Based Evaluation**: Optional task goals with automatic reward calculation
72
+ - **Configurable**: Customize app configurations and behavior
73
+ - **BrowserGym Integration**: Built on top of BrowserGym for robust browser interaction
74
+
75
+ ## Directory Structure
76
+
77
+ ```
78
+ openapp_env/
79
+ ├── __init__.py # Package exports
80
+ ├── client.py # HTTP client for connecting to OpenApp
81
+ ├── models.py # Data models for actions and observations
82
+ ├── pyproject.toml # Package dependencies and configuration
83
+ ├── openenv.yaml # OpenEnv environment configuration
84
+ ├── test_openapp_env.py # Unit tests for environment structure
85
+ ├── README.md # This file
86
+ ├── IMPLEMENTATION.md # Implementation details and design decisions
87
+ ├── example_usage.py # Basic usage example (legacy)
88
+ ├── assets/ # Images and media
89
+ │ ├── OpenApps_OpenEnv_RL.png # Environment overview diagram
90
+ │ └── openapps-demo.gif # Demo animation
91
+ └── server/ # Server-side environment implementation
92
+ ├── __init__.py
93
+ ├── app.py # FastAPI server application
94
+ ├── openapp_environment.py # Core environment logic (BrowserGym + OpenApps)
95
+ ├── Dockerfile # Docker image definition
96
+ └── start.sh # Container startup script (runs both servers)
97
+ ```
98
+
99
+ **Key Components:**
100
+
101
+ - **client.py**: `OpenAppEnv` class that extends `HTTPEnvClient` for remote environment interaction
102
+ - **models.py**: `OpenAppAction` and `OpenAppObservation` dataclasses with validation
103
+ - **server/openapp_environment.py**: `OpenAppEnvironment` class that wraps BrowserGym and OpenApps
104
+ - **server/app.py**: FastAPI server that exposes the environment via HTTP endpoints
105
+ - **server/Dockerfile**: Self-contained Docker image with OpenApps server and FastAPI server
106
+ - **server/start.sh**: Startup script that launches both OpenApps (port 5001) and FastAPI (port 8000)
107
+
108
+ ## Installation
109
+
110
+ There are two ways to use the OpenApp environment: **Docker mode** (recommended, fully self-contained) or **Local mode** (requires manual server setup).
111
+
112
+ ### Option 1: Docker Mode (Recommended)
113
+
114
+ Docker mode is fully self-contained and handles all dependencies automatically. No local installation required!
115
+
116
+ **Step 1: Build the Docker image**
117
+
118
+ The Docker image can be built in standalone mode using only public base images:
119
+
120
+ ```bash
121
+ # Build from the environment directory
122
+ cd envs/openapp_env
123
+ docker build -t openapp-env:latest -f server/Dockerfile .
124
+ ```
125
+
126
+ **Note for Meta/Corporate Networks:** If you're behind a proxy (HTTP_PROXY/HTTPS_PROXY set), you may need to bypass it for localhost connections:
127
+ ```bash
128
+ export NO_PROXY=localhost,127.0.0.1
129
+ cd envs/openapp_env
130
+ docker build -t openapp-env:latest -f server/Dockerfile .
131
+ ```
132
+
133
+ **What gets installed in Docker:**
134
+ - **OpenEnv core**: Installed as a dependency
135
+ - **OpenApps**: Cloned from GitHub and installed (runs server inside container)
136
+ - **Core packages**: FastAPI, Uvicorn, Pydantic, Requests (from pyproject.toml)
137
+ - **BrowserGym**: For browser automation
138
+ - **Playwright**: Chromium browser for UI interaction
139
+ - **Web interface support**: Enabled by default via `ENABLE_WEB_INTERFACE=true`
140
+
141
+ **How Docker mode works:**
142
+ The Docker container runs TWO services automatically:
143
+ 1. **OpenApps server** (port 5001) - Provides the web applications (calendar, todo, messenger, maps)
144
+ 2. **FastAPI server** (port 8000) - Exposes the OpenEnv HTTP API
145
+
146
+ Both servers start automatically when the container launches. You only interact with port 8000.
147
+
148
+ **Build details:**
149
+ - Base image: `python:3.11-slim` (public)
150
+ - Installation: Uses `pip install -e .` with pyproject.toml
151
+ - System deps: Playwright/Chromium dependencies for browser automation
152
+ - Size: ~5.7GB (includes Chromium browser and all dependencies)
153
+
154
+ **Step 2: Run the example**
155
+ ```bash
156
+ # For Meta/Corporate Networks with proxy, also set NO_PROXY:
157
+ export NO_PROXY=localhost,127.0.0.1
158
+
159
+ python examples/openapp_example.py --mode docker
160
+ ```
161
+
162
+ **Note:** For Docker mode, you only need Python installed locally to run the example script. All environment dependencies are inside the Docker container.
163
+
164
+ ### Option 2: Local Mode
165
+
166
+ Local mode requires manual setup of the OpenApps server. This mode is useful for development or when you need to customize the OpenApps configuration.
167
+
168
+ **Prerequisites:**
169
+ - Python 3.11+ installed
170
+ - UV package manager (recommended) or pip
171
+
172
+ **Step 1: Install openapp_env**
173
+ ```bash
174
+ cd envs/openapp_env
175
+ pip install -e .
176
+ ```
177
+
178
+ This installs the environment package along with dependencies (BrowserGym, Playwright, etc.).
179
+
180
+ **Step 2: Install Playwright browsers**
181
+ ```bash
182
+ playwright install chromium
183
+ ```
184
+
185
+ **Step 3: Clone and set up OpenApps** (for running the server)
186
+ ```bash
187
+ # Clone OpenApps repository
188
+ git clone https://github.com/facebookresearch/OpenApps.git
189
+ cd OpenApps
190
+
191
+ # Install dependencies
192
+ uv sync # or: pip install -e .
193
+ ```
194
+
195
+ **Why do I need the OpenApps repository?**
196
+
197
+ The OpenApps Python package (installed via pip in Step 1) provides the library code, but the repository contains:
198
+ - `launch.py` - The server startup script
199
+ - `config/` - Hydra configuration files
200
+ - Application templates and assets
201
+
202
+ In Docker mode, all of this is included in the container, so you don't need to clone anything.
203
+
204
+ ## Quick Start
205
+
206
+ ### Running with Docker (Recommended)
207
+
208
+ Docker mode is the easiest way - everything is automated:
209
+
210
+ ```bash
211
+ # For Meta/Corporate networks with proxy, set NO_PROXY first:
212
+ export NO_PROXY=localhost,127.0.0.1
213
+
214
+ # Run the example
215
+ python examples/openapp_example.py --mode docker
216
+ ```
217
+
218
+ The Docker container automatically:
219
+ - Starts the OpenApps server (port 5001)
220
+ - Starts the FastAPI server (port 8000)
221
+ - Manages both services for you
222
+
223
+ No manual server setup required!
224
+
225
+ **What happens inside the container:**
226
+
227
+ When you run `from_docker_image()`, the following happens automatically:
228
+
229
+ 1. **Container Startup** (`/app/start.sh` runs):
230
+ ```bash
231
+ # Launches OpenApps server in background
232
+ cd /app/openapps
233
+ python launch.py &
234
+
235
+ # Waits for port 5001 to be ready
236
+ # Then starts FastAPI server
237
+ uvicorn openapp_env.server.app:app --host 0.0.0.0 --port 8000
238
+ ```
239
+
240
+ 2. **Your client code** interacts only with port 8000:
241
+ ```python
242
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
243
+ # Client -> FastAPI (port 8000) -> OpenApps (port 5001)
244
+ ```
245
+
246
+ 3. **On cleanup**, both servers are automatically stopped when the container is removed.
247
+
248
+ ### Running Locally
249
+
250
+ For local usage, you need the OpenApps repository to run the server:
251
+
252
+ **Step 1: Clone OpenApps (if you haven't already)**
253
+ ```bash
254
+ git clone https://github.com/facebookresearch/OpenApps.git
255
+ cd OpenApps
256
+ uv sync
257
+ ```
258
+
259
+ **Step 2: Start OpenApps Server** (in terminal 1)
260
+
261
+ To run the server in **headless mode** (no browser window):
262
+ ```bash
263
+ cd OpenApps # or wherever you cloned it
264
+ uv run launch.py
265
+
266
+ # or instead of the uv run you can use the Python command:
267
+ python OpenApps/launch.py
268
+ ```
269
+
270
+ To run the server with **visible browser** for visualization:
271
+ ```bash
272
+ cd OpenApps
273
+ python OpenApps/launch.py browsergym_env_args.headless=False
274
+ ```
275
+
276
+ Wait for the server to start (you'll see "Port 5001 is available" or similar).
277
+
278
+ **Step 3: Run your code** (in terminal 2)
279
+ ```bash
280
+ export OPENAPPS_URL=http://localhost:5001
281
+ python examples/openapp_example.py --mode local
282
+ ```
283
+
284
+ **Note:** The OpenApps Python package (installed via pip) provides the modules, but you need the full repository to run launch.py with its config files.
285
+
286
+ ### Example Script
287
+
288
+ ```bash
289
+ # Run with Docker (recommended)
290
+ python examples/openapp_example.py --mode docker
291
+
292
+ # Run locally (requires OpenApps server running)
293
+ export OPENAPPS_URL=http://localhost:5001
294
+ python examples/openapp_example.py --mode local
295
+
296
+ # Show browser window to visualize agent actions
297
+ python examples/openapp_example.py --mode local --show-browser
298
+
299
+ # Run with custom number of steps
300
+ python examples/openapp_example.py --mode docker --num-steps 20
301
+
302
+ # See all options
303
+ python examples/openapp_example.py --help
304
+ ```
305
+
306
+ ### Visualizing Agent Interactions
307
+
308
+ There are multiple ways to see what the agent is doing:
309
+
310
+ **Option 1: Show Browser Window (Local Mode)**
311
+
312
+ The key is to start the OpenApps server with visualization enabled:
313
+
314
+ ```bash
315
+ # Terminal 1: Start OpenApps server with visible browser
316
+ cd OpenApps
317
+ python OpenApps/launch.py browsergym_env_args.headless=False
318
+
319
+ # Terminal 2: Run your agent code
320
+ export OPENAPPS_URL=http://localhost:5001
321
+ python examples/openapp_example.py --mode local
322
+ ```
323
+
324
+ **Important:** The browser visualization is controlled by the OpenApps server, not the client. You must launch the server with `browsergym_env_args.headless=False` to see the browser window.
325
+
326
+ **Option 2: Access Web Interface Directly**
327
+
328
+ While the OpenApps server is running, open your browser to:
329
+ - Main page: `http://localhost:5001`
330
+ - Calendar: `http://localhost:5001/calendar`
331
+ - Todo: `http://localhost:5001/todo`
332
+ - Messenger: `http://localhost:5001/messages`
333
+ - Maps: `http://localhost:5001/maps`
334
+
335
+ **Option 3: Docker Web Interface**
336
+
337
+ When running in Docker mode, you can also access a web interface for manual testing:
338
+
339
+ ```bash
340
+ # Start a container and keep it running
341
+ docker run -d -p 8000:8000 openapp-env:latest
342
+
343
+ # Access the web interface
344
+ # - Interactive UI: http://localhost:8000/web
345
+ # - API docs: http://localhost:8000/docs
346
+ # - OpenApps (internal): http://localhost:5001 (inside container)
347
+ ```
348
+
349
+ **Note:** In Docker mode, the OpenApps server runs inside the container and is not directly accessible from your host machine. The FastAPI server at port 8000 acts as a proxy to interact with OpenApps.
350
+
351
+ ### Basic Usage
352
+
353
+ ```python
354
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
355
+
356
+ # Create environment from Docker image
357
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
358
+
359
+ # Reset to initial state
360
+ result = client.reset()
361
+ print(f"Starting URL: {result.observation.url}")
362
+
363
+ # Navigate to calendar app
364
+ result = client.step(OpenAppAction(
365
+ action_type="goto",
366
+ url="http://localhost:5001/calendar"
367
+ ))
368
+
369
+ # Click on a button (example bid)
370
+ result = client.step(OpenAppAction(
371
+ action_type="click",
372
+ bid="add-event-btn"
373
+ ))
374
+
375
+ # Fill in a form field
376
+ result = client.step(OpenAppAction(
377
+ action_type="fill",
378
+ bid="event-title-input",
379
+ text="Team Meeting"
380
+ ))
381
+
382
+ print(f"Reward: {result.reward}")
383
+ print(f"Done: {result.done}")
384
+
385
+ # Cleanup
386
+ client.close()
387
+ ```
388
+
389
+ ### Action Types
390
+
391
+ The environment supports the following action types:
392
+
393
+ - **click**: Click on an element
394
+ - Required: `bid` (BrowserGym element ID)
395
+
396
+ - **fill**: Fill a text input field
397
+ - Required: `bid`, `text`
398
+
399
+ - **select_option**: Select from dropdown
400
+ - Required: `bid`, `value`
401
+
402
+ - **goto**: Navigate to a URL
403
+ - Required: `url`
404
+
405
+ - **scroll**: Scroll the page
406
+ - Required: `direction` ("up" or "down")
407
+
408
+ - **send_keys**: Send keyboard input
409
+ - Required: `text`
410
+
411
+ - **noop**: No operation
412
+
413
+ ### Observations
414
+
415
+ Each observation includes:
416
+
417
+ - **html**: Current page HTML content
418
+ - **url**: Current page URL
419
+ - **open_pages_urls**: List of all open page URLs
420
+ - **active_page_index**: Index of currently active page
421
+ - **screenshot**: Base64-encoded screenshot (optional)
422
+ - **axtree_txt**: Accessibility tree for element interaction
423
+ - **app_state**: Current state of all apps (calendar events, todos, messages, etc.)
424
+ - **task_info**: Information about current task (if using tasks)
425
+ - **last_action_error**: Error message if last action failed
426
+
427
+ ## Configuration
428
+
429
+ ### Environment Parameters
430
+
431
+ ```python
432
+ from envs.openapp_env.server.openapp_environment import OpenAppEnvironment
433
+
434
+ env = OpenAppEnvironment(
435
+ web_app_port=5001, # Port for OpenApps server
436
+ headless=True, # Run browser in headless mode
437
+ task_name="add_meeting", # Optional task name
438
+ apps_config={}, # App-specific configuration
439
+ max_steps=50, # Maximum steps per episode
440
+ )
441
+ ```
442
+
443
+ **Note:** OpenApps is automatically detected from the installed Python package. You can optionally override with `openapps_path` parameter or `OPENAPPS_PATH` environment variable if needed.
444
+
445
+ ## Tasks and Rewards
446
+
447
+ The environment can be configured with specific tasks from OpenApps. Tasks define:
448
+ - Goal state (e.g., "Add a meeting with Dennis to the calendar")
449
+ - Reward function based on app state changes
450
+ - Success criteria
451
+
452
+ See [OpenApps documentation](https://facebookresearch.github.io/OpenApps/) for available tasks.
453
+
454
+ ## Example: Task-Based Training
455
+
456
+ ```python
457
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
458
+
459
+ # Create environment with a specific task
460
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
461
+
462
+ # The task will guide the agent toward a specific goal
463
+ # Rewards will be based on progress toward completing the task
464
+ result = client.reset()
465
+
466
+ # Agent interacts to complete the task
467
+ # ... agent logic here ...
468
+
469
+ client.close()
470
+ ```
471
+
472
+ ## Development
473
+
474
+ ### Running Server Locally (without Docker)
475
+
476
+ ```bash
477
+ cd envs/openapp_env
478
+ uv run server
479
+ ```
480
+
481
+ The server will start at `http://localhost:8000`
482
+
483
+ ### Testing
484
+
485
+ ```python
486
+ from openapp_env.server.openapp_environment import OpenAppEnvironment
487
+ from openapp_env.models import OpenAppAction
488
+
489
+ def test_environment():
490
+ env = OpenAppEnvironment()
491
+
492
+ # Test reset
493
+ obs = env.reset()
494
+ assert obs.url != ""
495
+
496
+ # Test step
497
+ action = OpenAppAction(action_type="noop")
498
+ obs = env.step(action)
499
+ assert env.state.step_count == 1
500
+
501
+ # Cleanup
502
+ env.close()
503
+
504
+ test_environment()
505
+ ```
506
+
507
+ ## Attribution
508
+
509
+ This environment integrates:
510
+ - [OpenApps](https://github.com/facebookresearch/OpenApps) - Web application simulation framework
511
+ - [BrowserGym](https://github.com/ServiceNow/BrowserGym) - Browser automation environment
512
+
513
+ ## Troubleshooting
514
+
515
+ ### Docker Build Issues
516
+
517
+ **Error: `Container did not become ready`**
518
+
519
+ If you're behind a corporate proxy (Meta/Facebook networks), set `NO_PROXY`:
520
+ ```bash
521
+ export NO_PROXY=localhost,127.0.0.1
522
+ docker build -t openapp-env:latest -f envs/openapp_env/server/Dockerfile .
523
+ ```
524
+
525
+ **Error: `Environment variable 'USER' not found`**
526
+
527
+ This is automatically handled in the Dockerfile with `ENV USER=root`. If you see this, rebuild the image.
528
+
529
+ **Container exits immediately**
530
+
531
+ Check the logs to see which server failed:
532
+ ```bash
533
+ docker logs <container-id>
534
+ ```
535
+
536
+ Common causes:
537
+ - OpenApps server failed to start (check for port conflicts)
538
+ - Missing dependencies (rebuild with `--no-cache`)
539
+
540
+ ### Local Mode Issues
541
+
542
+ **Error: `OPENAPPS_URL not set`**
543
+
544
+ Set the environment variable before running:
545
+ ```bash
546
+ export OPENAPPS_URL=http://localhost:5001
547
+ python examples/openapp_example.py --mode local
548
+ ```
549
+
550
+ **Error: `Connection refused to localhost:5001`**
551
+
552
+ Make sure the OpenApps server is running:
553
+ ```bash
554
+ cd OpenApps
555
+ uv run launch.py
556
+ ```
557
+
558
+ **Browser visualization not working**
559
+
560
+ The visualization is controlled by the **server**, not the client:
561
+ ```bash
562
+ # Start server with visible browser
563
+ cd OpenApps
564
+ python launch.py browsergym_env_args.headless=False
565
+ ```
566
+
567
+ ### Performance Issues
568
+
569
+ **Docker container is slow**
570
+
571
+ The container runs both a full Chromium browser and web applications. For faster performance:
572
+ - Increase Docker memory allocation (6GB+ recommended)
573
+ - Use headless mode (default)
574
+ - Reduce `max_steps` in environment configuration
575
+
576
+ **Large Docker image size**
577
+
578
+ The image is ~5.7GB due to:
579
+ - Chromium browser (~1.5GB)
580
+ - OpenApps dependencies (~2GB)
581
+ - BrowserGym and ML libraries (~2GB)
582
+
583
+ This is expected for a full browser automation environment.
584
+
585
+ ## License
586
+
587
+ BSD 3-Clause License (see LICENSE file in OpenEnv root directory)
588
+
589
+ ## Citation
590
+
591
+ If you use this environment in your research, please cite both OpenEnv and OpenApps:
592
+
593
+ ```bibtex
594
+ @article{ullrich2025openapps0,
595
+ title = {OpenApps: Simulating Environment Variations to Measure UI-Agent Reliability},
596
+ author = {Karen Ullrich and Jingtong Su and Claudia Shi and Arjun Subramonian and Amir Bar and Ivan Evtimov and Nikolaos Tsilivis and Randall Balestriero and Julia Kempe and Mark Ibrahim},
597
+ year = {2025},
598
+ journal = {arXiv preprint arXiv: 2511.20766}
599
+ }
600
+ ```
__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """OpenApp Environment - Web application simulation environment for UI agents."""
8
+
9
+ from .client import OpenAppEnv
10
+ from .models import OpenAppAction, OpenAppObservation
11
+
12
+ __all__ = ["OpenAppAction", "OpenAppObservation", "OpenAppEnv"]
assets/01-messages.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b4333519dedb001fdc95c47430349e1e33f03daf0082b3d80d2e66f604e812e
3
+ size 10889481
assets/02-editor.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:49677c425607944fdcc4a35f0cefa55115b34fa1c61b8597074e5920e6be31ad
3
+ size 11875181
assets/03-calendar.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9b4011bb80014623250bd375a3c52012f0a43469dcff966c0fe538727cd9c194
3
+ size 10553742
assets/04-todo.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6b2231f1e2587ddabbf59fb793693e645e907cf6d7dd5d6bd9c79cf781f016e6
3
+ size 9591496
assets/OpenApps_OpenEnv_RL.png ADDED

Git LFS Details

  • SHA256: 72761e90fedcff4a0b314814ed79ec398e4a724773c7be9f9c4ea3f1b23845f2
  • Pointer size: 132 Bytes
  • Size of remote file: 6.82 MB
assets/demo-showcase.html ADDED
@@ -0,0 +1,624 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenApp + OpenEnv Demo Showcase</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary-color: #6366f1;
11
+ --primary-dark: #4f46e5;
12
+ --secondary-color: #10b981;
13
+ --accent-color: #f59e0b;
14
+ --bg-dark: #0f172a;
15
+ --bg-card: #1e293b;
16
+ --bg-card-hover: #334155;
17
+ --text-primary: #f8fafc;
18
+ --text-secondary: #94a3b8;
19
+ --gradient-1: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
20
+ --gradient-2: linear-gradient(135deg, #10b981 0%, #14b8a6 100%);
21
+ --gradient-3: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);
22
+ --gradient-4: linear-gradient(135deg, #ec4899 0%, #f43f5e 100%);
23
+ }
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
33
+ background: var(--bg-dark);
34
+ color: var(--text-primary);
35
+ min-height: 100vh;
36
+ overflow-x: hidden;
37
+ }
38
+
39
+ /* Animated background */
40
+ .bg-animation {
41
+ position: fixed;
42
+ top: 0;
43
+ left: 0;
44
+ width: 100%;
45
+ height: 100%;
46
+ z-index: -1;
47
+ overflow: hidden;
48
+ }
49
+
50
+ .bg-animation::before {
51
+ content: '';
52
+ position: absolute;
53
+ top: -50%;
54
+ left: -50%;
55
+ width: 200%;
56
+ height: 200%;
57
+ background: radial-gradient(circle at 20% 80%, rgba(99, 102, 241, 0.1) 0%, transparent 50%),
58
+ radial-gradient(circle at 80% 20%, rgba(16, 185, 129, 0.1) 0%, transparent 50%),
59
+ radial-gradient(circle at 40% 40%, rgba(245, 158, 11, 0.05) 0%, transparent 40%);
60
+ animation: bgRotate 30s linear infinite;
61
+ }
62
+
63
+ @keyframes bgRotate {
64
+ 0% { transform: rotate(0deg); }
65
+ 100% { transform: rotate(360deg); }
66
+ }
67
+
68
+ /* Header */
69
+ header {
70
+ text-align: center;
71
+ padding: 4rem 2rem 3rem;
72
+ position: relative;
73
+ }
74
+
75
+ .logo-container {
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ gap: 1rem;
80
+ margin-bottom: 1.5rem;
81
+ }
82
+
83
+ .logo-img {
84
+ width: 80px;
85
+ height: 80px;
86
+ border-radius: 16px;
87
+ box-shadow: 0 10px 40px rgba(99, 102, 241, 0.3);
88
+ }
89
+
90
+ h1 {
91
+ font-size: 3.5rem;
92
+ font-weight: 800;
93
+ background: linear-gradient(135deg, #fff 0%, #a5b4fc 50%, #6366f1 100%);
94
+ -webkit-background-clip: text;
95
+ -webkit-text-fill-color: transparent;
96
+ background-clip: text;
97
+ margin-bottom: 1rem;
98
+ letter-spacing: -0.02em;
99
+ }
100
+
101
+ .subtitle {
102
+ font-size: 1.25rem;
103
+ color: var(--text-secondary);
104
+ max-width: 700px;
105
+ margin: 0 auto 2rem;
106
+ line-height: 1.6;
107
+ }
108
+
109
+ .badge-container {
110
+ display: flex;
111
+ justify-content: center;
112
+ gap: 1rem;
113
+ flex-wrap: wrap;
114
+ }
115
+
116
+ .badge {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: 0.5rem;
120
+ padding: 0.5rem 1rem;
121
+ background: var(--bg-card);
122
+ border-radius: 9999px;
123
+ font-size: 0.875rem;
124
+ font-weight: 500;
125
+ border: 1px solid rgba(255, 255, 255, 0.1);
126
+ }
127
+
128
+ .badge-icon {
129
+ width: 18px;
130
+ height: 18px;
131
+ }
132
+
133
+ /* Main content */
134
+ main {
135
+ max-width: 1400px;
136
+ margin: 0 auto;
137
+ padding: 2rem;
138
+ }
139
+
140
+ /* Section title */
141
+ .section-title {
142
+ text-align: center;
143
+ margin-bottom: 3rem;
144
+ }
145
+
146
+ .section-title h2 {
147
+ font-size: 2rem;
148
+ font-weight: 700;
149
+ margin-bottom: 0.5rem;
150
+ }
151
+
152
+ .section-title p {
153
+ color: var(--text-secondary);
154
+ }
155
+
156
+ /* Demo grid */
157
+ .demo-grid {
158
+ display: grid;
159
+ grid-template-columns: repeat(2, 1fr);
160
+ gap: 2rem;
161
+ margin-bottom: 4rem;
162
+ }
163
+
164
+ @media (max-width: 1024px) {
165
+ .demo-grid {
166
+ grid-template-columns: 1fr;
167
+ }
168
+ }
169
+
170
+ /* Demo card */
171
+ .demo-card {
172
+ background: var(--bg-card);
173
+ border-radius: 20px;
174
+ overflow: hidden;
175
+ border: 1px solid rgba(255, 255, 255, 0.1);
176
+ transition: all 0.3s ease;
177
+ position: relative;
178
+ }
179
+
180
+ .demo-card:hover {
181
+ transform: translateY(-5px);
182
+ border-color: rgba(99, 102, 241, 0.5);
183
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4),
184
+ 0 0 40px rgba(99, 102, 241, 0.1);
185
+ }
186
+
187
+ .demo-card::before {
188
+ content: '';
189
+ position: absolute;
190
+ top: 0;
191
+ left: 0;
192
+ right: 0;
193
+ height: 4px;
194
+ opacity: 0;
195
+ transition: opacity 0.3s ease;
196
+ }
197
+
198
+ .demo-card:nth-child(1)::before { background: var(--gradient-1); }
199
+ .demo-card:nth-child(2)::before { background: var(--gradient-2); }
200
+ .demo-card:nth-child(3)::before { background: var(--gradient-3); }
201
+ .demo-card:nth-child(4)::before { background: var(--gradient-4); }
202
+
203
+ .demo-card:hover::before {
204
+ opacity: 1;
205
+ }
206
+
207
+ .video-container {
208
+ position: relative;
209
+ width: 100%;
210
+ background: #000;
211
+ aspect-ratio: 16 / 10;
212
+ }
213
+
214
+ .video-container video {
215
+ width: 100%;
216
+ height: 100%;
217
+ object-fit: contain;
218
+ }
219
+
220
+ .play-overlay {
221
+ position: absolute;
222
+ top: 0;
223
+ left: 0;
224
+ width: 100%;
225
+ height: 100%;
226
+ display: flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ background: rgba(0, 0, 0, 0.4);
230
+ opacity: 0;
231
+ transition: opacity 0.3s ease;
232
+ cursor: pointer;
233
+ }
234
+
235
+ .video-container:hover .play-overlay {
236
+ opacity: 1;
237
+ }
238
+
239
+ .play-overlay.hidden {
240
+ display: none;
241
+ }
242
+
243
+ .play-button {
244
+ width: 80px;
245
+ height: 80px;
246
+ background: rgba(255, 255, 255, 0.95);
247
+ border-radius: 50%;
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
252
+ transition: transform 0.3s ease;
253
+ }
254
+
255
+ .play-button:hover {
256
+ transform: scale(1.1);
257
+ }
258
+
259
+ .play-button svg {
260
+ width: 32px;
261
+ height: 32px;
262
+ fill: var(--bg-dark);
263
+ margin-left: 4px;
264
+ }
265
+
266
+ .demo-info {
267
+ padding: 1.5rem;
268
+ }
269
+
270
+ .demo-header {
271
+ display: flex;
272
+ align-items: center;
273
+ gap: 0.75rem;
274
+ margin-bottom: 0.75rem;
275
+ }
276
+
277
+ .demo-icon {
278
+ width: 40px;
279
+ height: 40px;
280
+ border-radius: 10px;
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ font-size: 1.25rem;
285
+ }
286
+
287
+ .demo-card:nth-child(1) .demo-icon { background: var(--gradient-1); }
288
+ .demo-card:nth-child(2) .demo-icon { background: var(--gradient-2); }
289
+ .demo-card:nth-child(3) .demo-icon { background: var(--gradient-3); }
290
+ .demo-card:nth-child(4) .demo-icon { background: var(--gradient-4); }
291
+
292
+ .demo-title {
293
+ font-size: 1.25rem;
294
+ font-weight: 600;
295
+ }
296
+
297
+ .demo-description {
298
+ color: var(--text-secondary);
299
+ font-size: 0.95rem;
300
+ line-height: 1.6;
301
+ }
302
+
303
+ /* Features section */
304
+ .features-section {
305
+ margin-top: 4rem;
306
+ padding: 3rem;
307
+ background: var(--bg-card);
308
+ border-radius: 24px;
309
+ border: 1px solid rgba(255, 255, 255, 0.1);
310
+ }
311
+
312
+ .features-grid {
313
+ display: grid;
314
+ grid-template-columns: repeat(3, 1fr);
315
+ gap: 2rem;
316
+ margin-top: 2rem;
317
+ }
318
+
319
+ @media (max-width: 768px) {
320
+ .features-grid {
321
+ grid-template-columns: 1fr;
322
+ }
323
+ }
324
+
325
+ .feature-card {
326
+ text-align: center;
327
+ padding: 1.5rem;
328
+ }
329
+
330
+ .feature-icon {
331
+ width: 60px;
332
+ height: 60px;
333
+ background: var(--gradient-1);
334
+ border-radius: 16px;
335
+ display: flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ margin: 0 auto 1rem;
339
+ font-size: 1.5rem;
340
+ }
341
+
342
+ .feature-card:nth-child(2) .feature-icon { background: var(--gradient-2); }
343
+ .feature-card:nth-child(3) .feature-icon { background: var(--gradient-3); }
344
+
345
+ .feature-title {
346
+ font-size: 1.125rem;
347
+ font-weight: 600;
348
+ margin-bottom: 0.5rem;
349
+ }
350
+
351
+ .feature-desc {
352
+ color: var(--text-secondary);
353
+ font-size: 0.9rem;
354
+ line-height: 1.5;
355
+ }
356
+
357
+ /* Footer */
358
+ footer {
359
+ text-align: center;
360
+ padding: 3rem 2rem;
361
+ color: var(--text-secondary);
362
+ font-size: 0.875rem;
363
+ }
364
+
365
+ footer a {
366
+ color: var(--primary-color);
367
+ text-decoration: none;
368
+ }
369
+
370
+ footer a:hover {
371
+ text-decoration: underline;
372
+ }
373
+
374
+ /* Video controls styling */
375
+ video::-webkit-media-controls-panel {
376
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
377
+ }
378
+
379
+ /* Responsive adjustments */
380
+ @media (max-width: 640px) {
381
+ h1 {
382
+ font-size: 2.5rem;
383
+ }
384
+
385
+ .subtitle {
386
+ font-size: 1rem;
387
+ }
388
+
389
+ .demo-grid {
390
+ gap: 1.5rem;
391
+ }
392
+
393
+ main {
394
+ padding: 1rem;
395
+ }
396
+ }
397
+ </style>
398
+ </head>
399
+ <body>
400
+ <div class="bg-animation"></div>
401
+
402
+ <header>
403
+ <div class="logo-container">
404
+ <img src="OpenApps_OpenEnv_RL.png" alt="OpenApp Logo" class="logo-img">
405
+ </div>
406
+ <h1>OpenApp + OpenEnv</h1>
407
+ <p class="subtitle">
408
+ A powerful integration bringing realistic web application environments to reinforcement learning agents.
409
+ Watch AI agents interact with calendar, messaging, code editor, and task management apps.
410
+ </p>
411
+ <div class="badge-container">
412
+ <span class="badge">
413
+ <svg class="badge-icon" viewBox="0 0 24 24" fill="currentColor">
414
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
415
+ </svg>
416
+ BrowserGym Compatible
417
+ </span>
418
+ <span class="badge">
419
+ <svg class="badge-icon" viewBox="0 0 24 24" fill="currentColor">
420
+ <path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/>
421
+ </svg>
422
+ 5+ Web Apps
423
+ </span>
424
+ <span class="badge">
425
+ <svg class="badge-icon" viewBox="0 0 24 24" fill="currentColor">
426
+ <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
427
+ </svg>
428
+ RL Ready
429
+ </span>
430
+ </div>
431
+ </header>
432
+
433
+ <main>
434
+ <div class="section-title">
435
+ <h2>🎬 Demo Showcase</h2>
436
+ <p>Watch our AI agents perform real tasks in realistic web environments</p>
437
+ </div>
438
+
439
+ <div class="demo-grid">
440
+ <!-- Messenger Demo -->
441
+ <div class="demo-card">
442
+ <div class="video-container">
443
+ <video id="video1" controls preload="metadata" poster="">
444
+ <source src="01-messages.mov" type="video/quicktime">
445
+ <source src="01-messages.mov" type="video/mp4">
446
+ Your browser does not support the video tag.
447
+ </video>
448
+ <div class="play-overlay" onclick="playVideo('video1', this)">
449
+ <div class="play-button">
450
+ <svg viewBox="0 0 24 24">
451
+ <path d="M8 5v14l11-7z"/>
452
+ </svg>
453
+ </div>
454
+ </div>
455
+ </div>
456
+ <div class="demo-info">
457
+ <div class="demo-header">
458
+ <div class="demo-icon">💬</div>
459
+ <h3 class="demo-title">Messenger App</h3>
460
+ </div>
461
+ <p class="demo-description">
462
+ AI agent navigates conversations, types messages interactively, and sends them to contacts.
463
+ Demonstrates natural language input and real-time chat interactions.
464
+ </p>
465
+ </div>
466
+ </div>
467
+
468
+ <!-- Code Editor Demo -->
469
+ <div class="demo-card">
470
+ <div class="video-container">
471
+ <video id="video2" controls preload="metadata">
472
+ <source src="02-editor.mov" type="video/quicktime">
473
+ <source src="02-editor.mov" type="video/mp4">
474
+ Your browser does not support the video tag.
475
+ </video>
476
+ <div class="play-overlay" onclick="playVideo('video2', this)">
477
+ <div class="play-button">
478
+ <svg viewBox="0 0 24 24">
479
+ <path d="M8 5v14l11-7z"/>
480
+ </svg>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ <div class="demo-info">
485
+ <div class="demo-header">
486
+ <div class="demo-icon">💻</div>
487
+ <h3 class="demo-title">Code Editor</h3>
488
+ </div>
489
+ <p class="demo-description">
490
+ Agent creates files and writes a complete PyTorch training loop with syntax highlighting.
491
+ Shows code generation, file management, and save operations.
492
+ </p>
493
+ </div>
494
+ </div>
495
+
496
+ <!-- Calendar Demo -->
497
+ <div class="demo-card">
498
+ <div class="video-container">
499
+ <video id="video3" controls preload="metadata">
500
+ <source src="03-calendar.mov" type="video/quicktime">
501
+ <source src="03-calendar.mov" type="video/mp4">
502
+ Your browser does not support the video tag.
503
+ </video>
504
+ <div class="play-overlay" onclick="playVideo('video3', this)">
505
+ <div class="play-button">
506
+ <svg viewBox="0 0 24 24">
507
+ <path d="M8 5v14l11-7z"/>
508
+ </svg>
509
+ </div>
510
+ </div>
511
+ </div>
512
+ <div class="demo-info">
513
+ <div class="demo-header">
514
+ <div class="demo-icon">📅</div>
515
+ <h3 class="demo-title">Calendar App</h3>
516
+ </div>
517
+ <p class="demo-description">
518
+ Navigate between calendar and agenda views, browse events across months,
519
+ and view detailed event information. Perfect for scheduling tasks.
520
+ </p>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Todo Demo -->
525
+ <div class="demo-card">
526
+ <div class="video-container">
527
+ <video id="video4" controls preload="metadata">
528
+ <source src="04-todo.mov" type="video/quicktime">
529
+ <source src="04-todo.mov" type="video/mp4">
530
+ Your browser does not support the video tag.
531
+ </video>
532
+ <div class="play-overlay" onclick="playVideo('video4', this)">
533
+ <div class="play-button">
534
+ <svg viewBox="0 0 24 24">
535
+ <path d="M8 5v14l11-7z"/>
536
+ </svg>
537
+ </div>
538
+ </div>
539
+ </div>
540
+ <div class="demo-info">
541
+ <div class="demo-header">
542
+ <div class="demo-icon">✅</div>
543
+ <h3 class="demo-title">Todo Manager</h3>
544
+ </div>
545
+ <p class="demo-description">
546
+ Browse and manage task lists, edit task details, mark items complete,
547
+ and organize priorities. Demonstrates CRUD operations on structured data.
548
+ </p>
549
+ </div>
550
+ </div>
551
+ </div>
552
+
553
+ <!-- Features Section -->
554
+ <div class="features-section">
555
+ <div class="section-title">
556
+ <h2>✨ Key Features</h2>
557
+ <p>Why OpenApp + OpenEnv is perfect for AI agent research</p>
558
+ </div>
559
+ <div class="features-grid">
560
+ <div class="feature-card">
561
+ <div class="feature-icon">🎮</div>
562
+ <h4 class="feature-title">Gymnasium Compatible</h4>
563
+ <p class="feature-desc">
564
+ Standard RL interface with observations, actions, and rewards.
565
+ Drop-in replacement for existing training pipelines.
566
+ </p>
567
+ </div>
568
+ <div class="feature-card">
569
+ <div class="feature-icon">🌐</div>
570
+ <h4 class="feature-title">Real Web Apps</h4>
571
+ <p class="feature-desc">
572
+ Authentic web applications with HTML, CSS, and JavaScript.
573
+ No simplified simulations – real browser interactions.
574
+ </p>
575
+ </div>
576
+ <div class="feature-card">
577
+ <div class="feature-icon">🔄</div>
578
+ <h4 class="feature-title">Configurable Tasks</h4>
579
+ <p class="feature-desc">
580
+ YAML-based configuration for custom scenarios, data, and rewards.
581
+ Easily create new training environments.
582
+ </p>
583
+ </div>
584
+ </div>
585
+ </div>
586
+ </main>
587
+
588
+ <footer>
589
+ <p>
590
+ Built for the <strong>OpenEnv Hackathon</strong> 🏆<br>
591
+ <a href="https://github.com/anthropics/anthropic-cookbook/tree/main/misc/openenv" target="_blank">OpenEnv Framework</a> •
592
+ <a href="https://github.com/ServiceNow/BrowserGym" target="_blank">BrowserGym</a>
593
+ </p>
594
+ </footer>
595
+
596
+ <script>
597
+ function playVideo(videoId, overlay) {
598
+ const video = document.getElementById(videoId);
599
+ video.play();
600
+ overlay.classList.add('hidden');
601
+ }
602
+
603
+ // Show overlay again when video ends or is paused
604
+ document.querySelectorAll('video').forEach(video => {
605
+ video.addEventListener('ended', function() {
606
+ const overlay = this.parentElement.querySelector('.play-overlay');
607
+ overlay.classList.remove('hidden');
608
+ });
609
+ });
610
+
611
+ // Add intersection observer for lazy loading
612
+ const videos = document.querySelectorAll('video');
613
+ const observer = new IntersectionObserver((entries) => {
614
+ entries.forEach(entry => {
615
+ if (entry.isIntersecting) {
616
+ entry.target.load();
617
+ }
618
+ });
619
+ }, { threshold: 0.25 });
620
+
621
+ videos.forEach(video => observer.observe(video));
622
+ </script>
623
+ </body>
624
+ </html>
assets/openapps-demo.gif ADDED

Git LFS Details

  • SHA256: 906ff418bc943999178e1e1e962fb6f7a78d7026429adcfcc624b7f7f18eb0f3
  • Pointer size: 132 Bytes
  • Size of remote file: 3.13 MB
client.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ OpenApp Environment HTTP Client.
9
+
10
+ This module provides the client for connecting to an OpenApp Environment server
11
+ over HTTP.
12
+ """
13
+
14
+ from typing import Any, Dict
15
+
16
+ # Support both in-repo and standalone imports
17
+ try:
18
+ # In-repo imports (when running from OpenEnv repository)
19
+ from openenv.core.client_types import StepResult
20
+ from openenv.core.env_server.types import State
21
+ from openenv.core.env_client import EnvClient
22
+ from .models import OpenAppAction, OpenAppObservation
23
+ except ImportError:
24
+ # Standalone imports (when environment is standalone with openenv-core from pip)
25
+ from openenv.core.client_types import StepResult
26
+ from openenv.core.env_server.types import State
27
+ from openenv.core.env_client import EnvClient
28
+ from openapp_env.models import OpenAppAction, OpenAppObservation
29
+
30
+
31
+ class OpenAppEnv(EnvClient[OpenAppAction, OpenAppObservation, State]):
32
+ """
33
+ HTTP client for the OpenApp Environment.
34
+
35
+ This client connects to an OpenAppEnvironment HTTP server and provides
36
+ methods to interact with it: reset(), step(), and state access.
37
+
38
+ The OpenApp environment simulates web applications (calendar, todo, messenger, maps)
39
+ and allows agents to interact with them using browser-based actions.
40
+
41
+ Example:
42
+ >>> # Connect to a running server
43
+ >>> client = OpenAppEnv(base_url="http://localhost:8000")
44
+ >>> result = client.reset()
45
+ >>> print(result.observation.url)
46
+ >>>
47
+ >>> # Click on an element
48
+ >>> result = client.step(OpenAppAction(action_type="click", bid="123"))
49
+ >>> print(result.observation.html)
50
+ >>> print(result.reward)
51
+
52
+ Example with Docker:
53
+ >>> # Automatically start container and connect
54
+ >>> client = OpenAppEnv.from_docker_image("openapp-env:latest")
55
+ >>> result = client.reset()
56
+ >>> # Fill a text field
57
+ >>> result = client.step(OpenAppAction(
58
+ ... action_type="fill",
59
+ ... bid="456",
60
+ ... text="Meeting with team"
61
+ ... ))
62
+ """
63
+
64
+ def _step_payload(self, action: OpenAppAction) -> Dict:
65
+ """
66
+ Convert OpenAppAction to JSON payload for step request.
67
+
68
+ Args:
69
+ action: OpenAppAction instance
70
+
71
+ Returns:
72
+ Dictionary representation suitable for JSON encoding
73
+ """
74
+ payload = {
75
+ "action_type": action.action_type,
76
+ }
77
+
78
+ # Add optional fields if present
79
+ if action.bid is not None:
80
+ payload["bid"] = action.bid
81
+ if action.text is not None:
82
+ payload["text"] = action.text
83
+ if action.value is not None:
84
+ payload["value"] = action.value
85
+ if action.url is not None:
86
+ payload["url"] = action.url
87
+ if action.direction is not None:
88
+ payload["direction"] = action.direction
89
+ if action.metadata:
90
+ payload["metadata"] = action.metadata
91
+
92
+ return payload
93
+
94
+ def _parse_result(self, payload: Dict) -> StepResult[OpenAppObservation]:
95
+ """
96
+ Parse server response into StepResult[OpenAppObservation].
97
+
98
+ Args:
99
+ payload: JSON response from server
100
+
101
+ Returns:
102
+ StepResult with OpenAppObservation
103
+ """
104
+ obs_data = payload.get("observation", {})
105
+ observation = OpenAppObservation(
106
+ html=obs_data.get("html", ""),
107
+ url=obs_data.get("url", ""),
108
+ open_pages_urls=obs_data.get("open_pages_urls", []),
109
+ active_page_index=obs_data.get("active_page_index", 0),
110
+ screenshot=obs_data.get("screenshot"),
111
+ axtree_txt=obs_data.get("axtree_txt", ""),
112
+ app_state=obs_data.get("app_state", {}),
113
+ task_info=obs_data.get("task_info"),
114
+ last_action_error=obs_data.get("last_action_error"),
115
+ done=payload.get("done", False),
116
+ reward=payload.get("reward"),
117
+ metadata=obs_data.get("metadata", {}),
118
+ )
119
+
120
+ return StepResult(
121
+ observation=observation,
122
+ reward=payload.get("reward"),
123
+ done=payload.get("done", False),
124
+ )
125
+
126
+ def _parse_state(self, payload: Dict) -> State:
127
+ """
128
+ Parse server response into State object.
129
+
130
+ Args:
131
+ payload: JSON response from /state endpoint
132
+
133
+ Returns:
134
+ State object with episode_id and step_count
135
+ """
136
+ return State(
137
+ episode_id=payload.get("episode_id"),
138
+ step_count=payload.get("step_count", 0),
139
+ )
envs/openapp_env/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ OpenApps
envs/openapp_env/README.md ADDED
@@ -0,0 +1,584 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: OpenApp Environment Server
3
+ emoji: 🌐
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv
12
+ - OpenApps
13
+ - BrowserGym
14
+ - UI-Agents
15
+ - Reinforcement-Learning
16
+ ---
17
+
18
+ <!--
19
+ Copyright (c) Meta Platforms, Inc. and affiliates.
20
+ All rights reserved.
21
+
22
+ This source code is licensed under the BSD-style license found in the
23
+ LICENSE file in the root directory of this source tree.
24
+ -->
25
+
26
+ <div align="center">
27
+
28
+ # OpenApp Environment
29
+
30
+ <img src="assets/OpenApps_OpenEnv_RL.png" alt="OpenApps Environment" width="800"/>
31
+
32
+ *A web application simulation environment for OpenEnv that wraps the [OpenApps](https://github.com/facebookresearch/OpenApps) framework and BrowserGym.*
33
+
34
+ </div>
35
+
36
+ ## Overview
37
+
38
+ The OpenApp environment provides a simulated web application ecosystem where agents can interact with various apps (calendar, todo, messenger, maps) using browser-based actions.
39
+
40
+ <div align="center">
41
+ <img src="assets/openapps-demo.gif" alt="OpenApps Demo" width="800"/>
42
+ </div>
43
+
44
+ This environment is ideal for:
45
+
46
+ - Training and evaluating UI agents
47
+ - Testing web automation strategies
48
+ - Researching human-computer interaction
49
+ - Developing multimodal agents
50
+
51
+ ## Features
52
+
53
+ - **Multiple Apps**: Interact with calendar, todo list, messenger, and map applications
54
+ - **Browser-Based Actions**: Click, fill forms, navigate, scroll, and more
55
+ - **Task-Based Evaluation**: Optional task goals with automatic reward calculation
56
+ - **Configurable**: Customize app configurations and behavior
57
+ - **BrowserGym Integration**: Built on top of BrowserGym for robust browser interaction
58
+
59
+ ## Directory Structure
60
+
61
+ ```
62
+ openapp_env/
63
+ ├── __init__.py # Package exports
64
+ ├── client.py # HTTP client for connecting to OpenApp
65
+ ├── models.py # Data models for actions and observations
66
+ ├── pyproject.toml # Package dependencies and configuration
67
+ ├── openenv.yaml # OpenEnv environment configuration
68
+ ├── test_openapp_env.py # Unit tests for environment structure
69
+ ├── README.md # This file
70
+ ├── IMPLEMENTATION.md # Implementation details and design decisions
71
+ ├── example_usage.py # Basic usage example (legacy)
72
+ ├── assets/ # Images and media
73
+ │ ├── OpenApps_OpenEnv_RL.png # Environment overview diagram
74
+ │ └── openapps-demo.gif # Demo animation
75
+ └── server/ # Server-side environment implementation
76
+ ├── __init__.py
77
+ ├── app.py # FastAPI server application
78
+ ├── openapp_environment.py # Core environment logic (BrowserGym + OpenApps)
79
+ ├── Dockerfile # Docker image definition
80
+ └── start.sh # Container startup script (runs both servers)
81
+ ```
82
+
83
+ **Key Components:**
84
+
85
+ - **client.py**: `OpenAppEnv` class that extends `HTTPEnvClient` for remote environment interaction
86
+ - **models.py**: `OpenAppAction` and `OpenAppObservation` dataclasses with validation
87
+ - **server/openapp_environment.py**: `OpenAppEnvironment` class that wraps BrowserGym and OpenApps
88
+ - **server/app.py**: FastAPI server that exposes the environment via HTTP endpoints
89
+ - **server/Dockerfile**: Self-contained Docker image with OpenApps server and FastAPI server
90
+ - **server/start.sh**: Startup script that launches both OpenApps (port 5001) and FastAPI (port 8000)
91
+
92
+ ## Installation
93
+
94
+ There are two ways to use the OpenApp environment: **Docker mode** (recommended, fully self-contained) or **Local mode** (requires manual server setup).
95
+
96
+ ### Option 1: Docker Mode (Recommended)
97
+
98
+ Docker mode is fully self-contained and handles all dependencies automatically. No local installation required!
99
+
100
+ **Step 1: Build the Docker image**
101
+
102
+ The Docker image can be built in standalone mode using only public base images:
103
+
104
+ ```bash
105
+ # Build from the environment directory
106
+ cd envs/openapp_env
107
+ docker build -t openapp-env:latest -f server/Dockerfile .
108
+ ```
109
+
110
+ **Note for Meta/Corporate Networks:** If you're behind a proxy (HTTP_PROXY/HTTPS_PROXY set), you may need to bypass it for localhost connections:
111
+ ```bash
112
+ export NO_PROXY=localhost,127.0.0.1
113
+ cd envs/openapp_env
114
+ docker build -t openapp-env:latest -f server/Dockerfile .
115
+ ```
116
+
117
+ **What gets installed in Docker:**
118
+ - **OpenEnv core**: Installed as a dependency
119
+ - **OpenApps**: Cloned from GitHub and installed (runs server inside container)
120
+ - **Core packages**: FastAPI, Uvicorn, Pydantic, Requests (from pyproject.toml)
121
+ - **BrowserGym**: For browser automation
122
+ - **Playwright**: Chromium browser for UI interaction
123
+ - **Web interface support**: Enabled by default via `ENABLE_WEB_INTERFACE=true`
124
+
125
+ **How Docker mode works:**
126
+ The Docker container runs TWO services automatically:
127
+ 1. **OpenApps server** (port 5001) - Provides the web applications (calendar, todo, messenger, maps)
128
+ 2. **FastAPI server** (port 8000) - Exposes the OpenEnv HTTP API
129
+
130
+ Both servers start automatically when the container launches. You only interact with port 8000.
131
+
132
+ **Build details:**
133
+ - Base image: `python:3.11-slim` (public)
134
+ - Installation: Uses `pip install -e .` with pyproject.toml
135
+ - System deps: Playwright/Chromium dependencies for browser automation
136
+ - Size: ~5.7GB (includes Chromium browser and all dependencies)
137
+
138
+ **Step 2: Run the example**
139
+ ```bash
140
+ # For Meta/Corporate Networks with proxy, also set NO_PROXY:
141
+ export NO_PROXY=localhost,127.0.0.1
142
+
143
+ python examples/openapp_example.py --mode docker
144
+ ```
145
+
146
+ **Note:** For Docker mode, you only need Python installed locally to run the example script. All environment dependencies are inside the Docker container.
147
+
148
+ ### Option 2: Local Mode
149
+
150
+ Local mode requires manual setup of the OpenApps server. This mode is useful for development or when you need to customize the OpenApps configuration.
151
+
152
+ **Prerequisites:**
153
+ - Python 3.11+ installed
154
+ - UV package manager (recommended) or pip
155
+
156
+ **Step 1: Install openapp_env**
157
+ ```bash
158
+ cd envs/openapp_env
159
+ pip install -e .
160
+ ```
161
+
162
+ This installs the environment package along with dependencies (BrowserGym, Playwright, etc.).
163
+
164
+ **Step 2: Install Playwright browsers**
165
+ ```bash
166
+ playwright install chromium
167
+ ```
168
+
169
+ **Step 3: Clone and set up OpenApps** (for running the server)
170
+ ```bash
171
+ # Clone OpenApps repository
172
+ git clone https://github.com/facebookresearch/OpenApps.git
173
+ cd OpenApps
174
+
175
+ # Install dependencies
176
+ uv sync # or: pip install -e .
177
+ ```
178
+
179
+ **Why do I need the OpenApps repository?**
180
+
181
+ The OpenApps Python package (installed via pip in Step 1) provides the library code, but the repository contains:
182
+ - `launch.py` - The server startup script
183
+ - `config/` - Hydra configuration files
184
+ - Application templates and assets
185
+
186
+ In Docker mode, all of this is included in the container, so you don't need to clone anything.
187
+
188
+ ## Quick Start
189
+
190
+ ### Running with Docker (Recommended)
191
+
192
+ Docker mode is the easiest way - everything is automated:
193
+
194
+ ```bash
195
+ # For Meta/Corporate networks with proxy, set NO_PROXY first:
196
+ export NO_PROXY=localhost,127.0.0.1
197
+
198
+ # Run the example
199
+ python examples/openapp_example.py --mode docker
200
+ ```
201
+
202
+ The Docker container automatically:
203
+ - Starts the OpenApps server (port 5001)
204
+ - Starts the FastAPI server (port 8000)
205
+ - Manages both services for you
206
+
207
+ No manual server setup required!
208
+
209
+ **What happens inside the container:**
210
+
211
+ When you run `from_docker_image()`, the following happens automatically:
212
+
213
+ 1. **Container Startup** (`/app/start.sh` runs):
214
+ ```bash
215
+ # Launches OpenApps server in background
216
+ cd /app/openapps
217
+ python launch.py &
218
+
219
+ # Waits for port 5001 to be ready
220
+ # Then starts FastAPI server
221
+ uvicorn openapp_env.server.app:app --host 0.0.0.0 --port 8000
222
+ ```
223
+
224
+ 2. **Your client code** interacts only with port 8000:
225
+ ```python
226
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
227
+ # Client -> FastAPI (port 8000) -> OpenApps (port 5001)
228
+ ```
229
+
230
+ 3. **On cleanup**, both servers are automatically stopped when the container is removed.
231
+
232
+ ### Running Locally
233
+
234
+ For local usage, you need the OpenApps repository to run the server:
235
+
236
+ **Step 1: Clone OpenApps (if you haven't already)**
237
+ ```bash
238
+ git clone https://github.com/facebookresearch/OpenApps.git
239
+ cd OpenApps
240
+ uv sync
241
+ ```
242
+
243
+ **Step 2: Start OpenApps Server** (in terminal 1)
244
+
245
+ To run the server in **headless mode** (no browser window):
246
+ ```bash
247
+ cd OpenApps # or wherever you cloned it
248
+ uv run launch.py
249
+
250
+ # or instead of the uv run you can use the Python command:
251
+ python OpenApps/launch.py
252
+ ```
253
+
254
+ To run the server with **visible browser** for visualization:
255
+ ```bash
256
+ cd OpenApps
257
+ python OpenApps/launch.py browsergym_env_args.headless=False
258
+ ```
259
+
260
+ Wait for the server to start (you'll see "Port 5001 is available" or similar).
261
+
262
+ **Step 3: Run your code** (in terminal 2)
263
+ ```bash
264
+ export OPENAPPS_URL=http://localhost:5001
265
+ python examples/openapp_example.py --mode local
266
+ ```
267
+
268
+ **Note:** The OpenApps Python package (installed via pip) provides the modules, but you need the full repository to run launch.py with its config files.
269
+
270
+ ### Example Script
271
+
272
+ ```bash
273
+ # Run with Docker (recommended)
274
+ python examples/openapp_example.py --mode docker
275
+
276
+ # Run locally (requires OpenApps server running)
277
+ export OPENAPPS_URL=http://localhost:5001
278
+ python examples/openapp_example.py --mode local
279
+
280
+ # Show browser window to visualize agent actions
281
+ python examples/openapp_example.py --mode local --show-browser
282
+
283
+ # Run with custom number of steps
284
+ python examples/openapp_example.py --mode docker --num-steps 20
285
+
286
+ # See all options
287
+ python examples/openapp_example.py --help
288
+ ```
289
+
290
+ ### Visualizing Agent Interactions
291
+
292
+ There are multiple ways to see what the agent is doing:
293
+
294
+ **Option 1: Show Browser Window (Local Mode)**
295
+
296
+ The key is to start the OpenApps server with visualization enabled:
297
+
298
+ ```bash
299
+ # Terminal 1: Start OpenApps server with visible browser
300
+ cd OpenApps
301
+ python OpenApps/launch.py browsergym_env_args.headless=False
302
+
303
+ # Terminal 2: Run your agent code
304
+ export OPENAPPS_URL=http://localhost:5001
305
+ python examples/openapp_example.py --mode local
306
+ ```
307
+
308
+ **Important:** The browser visualization is controlled by the OpenApps server, not the client. You must launch the server with `browsergym_env_args.headless=False` to see the browser window.
309
+
310
+ **Option 2: Access Web Interface Directly**
311
+
312
+ While the OpenApps server is running, open your browser to:
313
+ - Main page: `http://localhost:5001`
314
+ - Calendar: `http://localhost:5001/calendar`
315
+ - Todo: `http://localhost:5001/todo`
316
+ - Messenger: `http://localhost:5001/messages`
317
+ - Maps: `http://localhost:5001/maps`
318
+
319
+ **Option 3: Docker Web Interface**
320
+
321
+ When running in Docker mode, you can also access a web interface for manual testing:
322
+
323
+ ```bash
324
+ # Start a container and keep it running
325
+ docker run -d -p 8000:8000 openapp-env:latest
326
+
327
+ # Access the web interface
328
+ # - Interactive UI: http://localhost:8000/web
329
+ # - API docs: http://localhost:8000/docs
330
+ # - OpenApps (internal): http://localhost:5001 (inside container)
331
+ ```
332
+
333
+ **Note:** In Docker mode, the OpenApps server runs inside the container and is not directly accessible from your host machine. The FastAPI server at port 8000 acts as a proxy to interact with OpenApps.
334
+
335
+ ### Basic Usage
336
+
337
+ ```python
338
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
339
+
340
+ # Create environment from Docker image
341
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
342
+
343
+ # Reset to initial state
344
+ result = client.reset()
345
+ print(f"Starting URL: {result.observation.url}")
346
+
347
+ # Navigate to calendar app
348
+ result = client.step(OpenAppAction(
349
+ action_type="goto",
350
+ url="http://localhost:5001/calendar"
351
+ ))
352
+
353
+ # Click on a button (example bid)
354
+ result = client.step(OpenAppAction(
355
+ action_type="click",
356
+ bid="add-event-btn"
357
+ ))
358
+
359
+ # Fill in a form field
360
+ result = client.step(OpenAppAction(
361
+ action_type="fill",
362
+ bid="event-title-input",
363
+ text="Team Meeting"
364
+ ))
365
+
366
+ print(f"Reward: {result.reward}")
367
+ print(f"Done: {result.done}")
368
+
369
+ # Cleanup
370
+ client.close()
371
+ ```
372
+
373
+ ### Action Types
374
+
375
+ The environment supports the following action types:
376
+
377
+ - **click**: Click on an element
378
+ - Required: `bid` (BrowserGym element ID)
379
+
380
+ - **fill**: Fill a text input field
381
+ - Required: `bid`, `text`
382
+
383
+ - **select_option**: Select from dropdown
384
+ - Required: `bid`, `value`
385
+
386
+ - **goto**: Navigate to a URL
387
+ - Required: `url`
388
+
389
+ - **scroll**: Scroll the page
390
+ - Required: `direction` ("up" or "down")
391
+
392
+ - **send_keys**: Send keyboard input
393
+ - Required: `text`
394
+
395
+ - **noop**: No operation
396
+
397
+ ### Observations
398
+
399
+ Each observation includes:
400
+
401
+ - **html**: Current page HTML content
402
+ - **url**: Current page URL
403
+ - **open_pages_urls**: List of all open page URLs
404
+ - **active_page_index**: Index of currently active page
405
+ - **screenshot**: Base64-encoded screenshot (optional)
406
+ - **axtree_txt**: Accessibility tree for element interaction
407
+ - **app_state**: Current state of all apps (calendar events, todos, messages, etc.)
408
+ - **task_info**: Information about current task (if using tasks)
409
+ - **last_action_error**: Error message if last action failed
410
+
411
+ ## Configuration
412
+
413
+ ### Environment Parameters
414
+
415
+ ```python
416
+ from envs.openapp_env.server.openapp_environment import OpenAppEnvironment
417
+
418
+ env = OpenAppEnvironment(
419
+ web_app_port=5001, # Port for OpenApps server
420
+ headless=True, # Run browser in headless mode
421
+ task_name="add_meeting", # Optional task name
422
+ apps_config={}, # App-specific configuration
423
+ max_steps=50, # Maximum steps per episode
424
+ )
425
+ ```
426
+
427
+ **Note:** OpenApps is automatically detected from the installed Python package. You can optionally override with `openapps_path` parameter or `OPENAPPS_PATH` environment variable if needed.
428
+
429
+ ## Tasks and Rewards
430
+
431
+ The environment can be configured with specific tasks from OpenApps. Tasks define:
432
+ - Goal state (e.g., "Add a meeting with Dennis to the calendar")
433
+ - Reward function based on app state changes
434
+ - Success criteria
435
+
436
+ See [OpenApps documentation](https://facebookresearch.github.io/OpenApps/) for available tasks.
437
+
438
+ ## Example: Task-Based Training
439
+
440
+ ```python
441
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
442
+
443
+ # Create environment with a specific task
444
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
445
+
446
+ # The task will guide the agent toward a specific goal
447
+ # Rewards will be based on progress toward completing the task
448
+ result = client.reset()
449
+
450
+ # Agent interacts to complete the task
451
+ # ... agent logic here ...
452
+
453
+ client.close()
454
+ ```
455
+
456
+ ## Development
457
+
458
+ ### Running Server Locally (without Docker)
459
+
460
+ ```bash
461
+ cd envs/openapp_env
462
+ uv run server
463
+ ```
464
+
465
+ The server will start at `http://localhost:8000`
466
+
467
+ ### Testing
468
+
469
+ ```python
470
+ from openapp_env.server.openapp_environment import OpenAppEnvironment
471
+ from openapp_env.models import OpenAppAction
472
+
473
+ def test_environment():
474
+ env = OpenAppEnvironment()
475
+
476
+ # Test reset
477
+ obs = env.reset()
478
+ assert obs.url != ""
479
+
480
+ # Test step
481
+ action = OpenAppAction(action_type="noop")
482
+ obs = env.step(action)
483
+ assert env.state.step_count == 1
484
+
485
+ # Cleanup
486
+ env.close()
487
+
488
+ test_environment()
489
+ ```
490
+
491
+ ## Attribution
492
+
493
+ This environment integrates:
494
+ - [OpenApps](https://github.com/facebookresearch/OpenApps) - Web application simulation framework
495
+ - [BrowserGym](https://github.com/ServiceNow/BrowserGym) - Browser automation environment
496
+
497
+ ## Troubleshooting
498
+
499
+ ### Docker Build Issues
500
+
501
+ **Error: `Container did not become ready`**
502
+
503
+ If you're behind a corporate proxy (Meta/Facebook networks), set `NO_PROXY`:
504
+ ```bash
505
+ export NO_PROXY=localhost,127.0.0.1
506
+ docker build -t openapp-env:latest -f envs/openapp_env/server/Dockerfile .
507
+ ```
508
+
509
+ **Error: `Environment variable 'USER' not found`**
510
+
511
+ This is automatically handled in the Dockerfile with `ENV USER=root`. If you see this, rebuild the image.
512
+
513
+ **Container exits immediately**
514
+
515
+ Check the logs to see which server failed:
516
+ ```bash
517
+ docker logs <container-id>
518
+ ```
519
+
520
+ Common causes:
521
+ - OpenApps server failed to start (check for port conflicts)
522
+ - Missing dependencies (rebuild with `--no-cache`)
523
+
524
+ ### Local Mode Issues
525
+
526
+ **Error: `OPENAPPS_URL not set`**
527
+
528
+ Set the environment variable before running:
529
+ ```bash
530
+ export OPENAPPS_URL=http://localhost:5001
531
+ python examples/openapp_example.py --mode local
532
+ ```
533
+
534
+ **Error: `Connection refused to localhost:5001`**
535
+
536
+ Make sure the OpenApps server is running:
537
+ ```bash
538
+ cd OpenApps
539
+ uv run launch.py
540
+ ```
541
+
542
+ **Browser visualization not working**
543
+
544
+ The visualization is controlled by the **server**, not the client:
545
+ ```bash
546
+ # Start server with visible browser
547
+ cd OpenApps
548
+ python launch.py browsergym_env_args.headless=False
549
+ ```
550
+
551
+ ### Performance Issues
552
+
553
+ **Docker container is slow**
554
+
555
+ The container runs both a full Chromium browser and web applications. For faster performance:
556
+ - Increase Docker memory allocation (6GB+ recommended)
557
+ - Use headless mode (default)
558
+ - Reduce `max_steps` in environment configuration
559
+
560
+ **Large Docker image size**
561
+
562
+ The image is ~5.7GB due to:
563
+ - Chromium browser (~1.5GB)
564
+ - OpenApps dependencies (~2GB)
565
+ - BrowserGym and ML libraries (~2GB)
566
+
567
+ This is expected for a full browser automation environment.
568
+
569
+ ## License
570
+
571
+ BSD 3-Clause License (see LICENSE file in OpenEnv root directory)
572
+
573
+ ## Citation
574
+
575
+ If you use this environment in your research, please cite both OpenEnv and OpenApps:
576
+
577
+ ```bibtex
578
+ @article{ullrich2025openapps0,
579
+ title = {OpenApps: Simulating Environment Variations to Measure UI-Agent Reliability},
580
+ author = {Karen Ullrich and Jingtong Su and Claudia Shi and Arjun Subramonian and Amir Bar and Ivan Evtimov and Nikolaos Tsilivis and Randall Balestriero and Julia Kempe and Mark Ibrahim},
581
+ year = {2025},
582
+ journal = {arXiv preprint arXiv: 2511.20766}
583
+ }
584
+ ```
envs/openapp_env/__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """OpenApp Environment - Web application simulation environment for UI agents."""
8
+
9
+ from .client import OpenAppEnv
10
+ from .models import OpenAppAction, OpenAppObservation
11
+
12
+ __all__ = ["OpenAppAction", "OpenAppObservation", "OpenAppEnv"]
envs/openapp_env/assets/01-messages.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b4333519dedb001fdc95c47430349e1e33f03daf0082b3d80d2e66f604e812e
3
+ size 10889481
envs/openapp_env/assets/02-editor.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:49677c425607944fdcc4a35f0cefa55115b34fa1c61b8597074e5920e6be31ad
3
+ size 11875181
envs/openapp_env/assets/03-calendar.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9b4011bb80014623250bd375a3c52012f0a43469dcff966c0fe538727cd9c194
3
+ size 10553742
envs/openapp_env/assets/04-todo.mov ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6b2231f1e2587ddabbf59fb793693e645e907cf6d7dd5d6bd9c79cf781f016e6
3
+ size 9591496
envs/openapp_env/assets/OpenApps_OpenEnv_RL.png ADDED

Git LFS Details

  • SHA256: 72761e90fedcff4a0b314814ed79ec398e4a724773c7be9f9c4ea3f1b23845f2
  • Pointer size: 132 Bytes
  • Size of remote file: 6.82 MB
envs/openapp_env/assets/demo-showcase.html ADDED
@@ -0,0 +1,624 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenApp + OpenEnv Demo Showcase</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary-color: #6366f1;
11
+ --primary-dark: #4f46e5;
12
+ --secondary-color: #10b981;
13
+ --accent-color: #f59e0b;
14
+ --bg-dark: #0f172a;
15
+ --bg-card: #1e293b;
16
+ --bg-card-hover: #334155;
17
+ --text-primary: #f8fafc;
18
+ --text-secondary: #94a3b8;
19
+ --gradient-1: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
20
+ --gradient-2: linear-gradient(135deg, #10b981 0%, #14b8a6 100%);
21
+ --gradient-3: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);
22
+ --gradient-4: linear-gradient(135deg, #ec4899 0%, #f43f5e 100%);
23
+ }
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
33
+ background: var(--bg-dark);
34
+ color: var(--text-primary);
35
+ min-height: 100vh;
36
+ overflow-x: hidden;
37
+ }
38
+
39
+ /* Animated background */
40
+ .bg-animation {
41
+ position: fixed;
42
+ top: 0;
43
+ left: 0;
44
+ width: 100%;
45
+ height: 100%;
46
+ z-index: -1;
47
+ overflow: hidden;
48
+ }
49
+
50
+ .bg-animation::before {
51
+ content: '';
52
+ position: absolute;
53
+ top: -50%;
54
+ left: -50%;
55
+ width: 200%;
56
+ height: 200%;
57
+ background: radial-gradient(circle at 20% 80%, rgba(99, 102, 241, 0.1) 0%, transparent 50%),
58
+ radial-gradient(circle at 80% 20%, rgba(16, 185, 129, 0.1) 0%, transparent 50%),
59
+ radial-gradient(circle at 40% 40%, rgba(245, 158, 11, 0.05) 0%, transparent 40%);
60
+ animation: bgRotate 30s linear infinite;
61
+ }
62
+
63
+ @keyframes bgRotate {
64
+ 0% { transform: rotate(0deg); }
65
+ 100% { transform: rotate(360deg); }
66
+ }
67
+
68
+ /* Header */
69
+ header {
70
+ text-align: center;
71
+ padding: 4rem 2rem 3rem;
72
+ position: relative;
73
+ }
74
+
75
+ .logo-container {
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ gap: 1rem;
80
+ margin-bottom: 1.5rem;
81
+ }
82
+
83
+ .logo-img {
84
+ width: 80px;
85
+ height: 80px;
86
+ border-radius: 16px;
87
+ box-shadow: 0 10px 40px rgba(99, 102, 241, 0.3);
88
+ }
89
+
90
+ h1 {
91
+ font-size: 3.5rem;
92
+ font-weight: 800;
93
+ background: linear-gradient(135deg, #fff 0%, #a5b4fc 50%, #6366f1 100%);
94
+ -webkit-background-clip: text;
95
+ -webkit-text-fill-color: transparent;
96
+ background-clip: text;
97
+ margin-bottom: 1rem;
98
+ letter-spacing: -0.02em;
99
+ }
100
+
101
+ .subtitle {
102
+ font-size: 1.25rem;
103
+ color: var(--text-secondary);
104
+ max-width: 700px;
105
+ margin: 0 auto 2rem;
106
+ line-height: 1.6;
107
+ }
108
+
109
+ .badge-container {
110
+ display: flex;
111
+ justify-content: center;
112
+ gap: 1rem;
113
+ flex-wrap: wrap;
114
+ }
115
+
116
+ .badge {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: 0.5rem;
120
+ padding: 0.5rem 1rem;
121
+ background: var(--bg-card);
122
+ border-radius: 9999px;
123
+ font-size: 0.875rem;
124
+ font-weight: 500;
125
+ border: 1px solid rgba(255, 255, 255, 0.1);
126
+ }
127
+
128
+ .badge-icon {
129
+ width: 18px;
130
+ height: 18px;
131
+ }
132
+
133
+ /* Main content */
134
+ main {
135
+ max-width: 1400px;
136
+ margin: 0 auto;
137
+ padding: 2rem;
138
+ }
139
+
140
+ /* Section title */
141
+ .section-title {
142
+ text-align: center;
143
+ margin-bottom: 3rem;
144
+ }
145
+
146
+ .section-title h2 {
147
+ font-size: 2rem;
148
+ font-weight: 700;
149
+ margin-bottom: 0.5rem;
150
+ }
151
+
152
+ .section-title p {
153
+ color: var(--text-secondary);
154
+ }
155
+
156
+ /* Demo grid */
157
+ .demo-grid {
158
+ display: grid;
159
+ grid-template-columns: repeat(2, 1fr);
160
+ gap: 2rem;
161
+ margin-bottom: 4rem;
162
+ }
163
+
164
+ @media (max-width: 1024px) {
165
+ .demo-grid {
166
+ grid-template-columns: 1fr;
167
+ }
168
+ }
169
+
170
+ /* Demo card */
171
+ .demo-card {
172
+ background: var(--bg-card);
173
+ border-radius: 20px;
174
+ overflow: hidden;
175
+ border: 1px solid rgba(255, 255, 255, 0.1);
176
+ transition: all 0.3s ease;
177
+ position: relative;
178
+ }
179
+
180
+ .demo-card:hover {
181
+ transform: translateY(-5px);
182
+ border-color: rgba(99, 102, 241, 0.5);
183
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4),
184
+ 0 0 40px rgba(99, 102, 241, 0.1);
185
+ }
186
+
187
+ .demo-card::before {
188
+ content: '';
189
+ position: absolute;
190
+ top: 0;
191
+ left: 0;
192
+ right: 0;
193
+ height: 4px;
194
+ opacity: 0;
195
+ transition: opacity 0.3s ease;
196
+ }
197
+
198
+ .demo-card:nth-child(1)::before { background: var(--gradient-1); }
199
+ .demo-card:nth-child(2)::before { background: var(--gradient-2); }
200
+ .demo-card:nth-child(3)::before { background: var(--gradient-3); }
201
+ .demo-card:nth-child(4)::before { background: var(--gradient-4); }
202
+
203
+ .demo-card:hover::before {
204
+ opacity: 1;
205
+ }
206
+
207
+ .video-container {
208
+ position: relative;
209
+ width: 100%;
210
+ background: #000;
211
+ aspect-ratio: 16 / 10;
212
+ }
213
+
214
+ .video-container video {
215
+ width: 100%;
216
+ height: 100%;
217
+ object-fit: contain;
218
+ }
219
+
220
+ .play-overlay {
221
+ position: absolute;
222
+ top: 0;
223
+ left: 0;
224
+ width: 100%;
225
+ height: 100%;
226
+ display: flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ background: rgba(0, 0, 0, 0.4);
230
+ opacity: 0;
231
+ transition: opacity 0.3s ease;
232
+ cursor: pointer;
233
+ }
234
+
235
+ .video-container:hover .play-overlay {
236
+ opacity: 1;
237
+ }
238
+
239
+ .play-overlay.hidden {
240
+ display: none;
241
+ }
242
+
243
+ .play-button {
244
+ width: 80px;
245
+ height: 80px;
246
+ background: rgba(255, 255, 255, 0.95);
247
+ border-radius: 50%;
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
252
+ transition: transform 0.3s ease;
253
+ }
254
+
255
+ .play-button:hover {
256
+ transform: scale(1.1);
257
+ }
258
+
259
+ .play-button svg {
260
+ width: 32px;
261
+ height: 32px;
262
+ fill: var(--bg-dark);
263
+ margin-left: 4px;
264
+ }
265
+
266
+ .demo-info {
267
+ padding: 1.5rem;
268
+ }
269
+
270
+ .demo-header {
271
+ display: flex;
272
+ align-items: center;
273
+ gap: 0.75rem;
274
+ margin-bottom: 0.75rem;
275
+ }
276
+
277
+ .demo-icon {
278
+ width: 40px;
279
+ height: 40px;
280
+ border-radius: 10px;
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ font-size: 1.25rem;
285
+ }
286
+
287
+ .demo-card:nth-child(1) .demo-icon { background: var(--gradient-1); }
288
+ .demo-card:nth-child(2) .demo-icon { background: var(--gradient-2); }
289
+ .demo-card:nth-child(3) .demo-icon { background: var(--gradient-3); }
290
+ .demo-card:nth-child(4) .demo-icon { background: var(--gradient-4); }
291
+
292
+ .demo-title {
293
+ font-size: 1.25rem;
294
+ font-weight: 600;
295
+ }
296
+
297
+ .demo-description {
298
+ color: var(--text-secondary);
299
+ font-size: 0.95rem;
300
+ line-height: 1.6;
301
+ }
302
+
303
+ /* Features section */
304
+ .features-section {
305
+ margin-top: 4rem;
306
+ padding: 3rem;
307
+ background: var(--bg-card);
308
+ border-radius: 24px;
309
+ border: 1px solid rgba(255, 255, 255, 0.1);
310
+ }
311
+
312
+ .features-grid {
313
+ display: grid;
314
+ grid-template-columns: repeat(3, 1fr);
315
+ gap: 2rem;
316
+ margin-top: 2rem;
317
+ }
318
+
319
+ @media (max-width: 768px) {
320
+ .features-grid {
321
+ grid-template-columns: 1fr;
322
+ }
323
+ }
324
+
325
+ .feature-card {
326
+ text-align: center;
327
+ padding: 1.5rem;
328
+ }
329
+
330
+ .feature-icon {
331
+ width: 60px;
332
+ height: 60px;
333
+ background: var(--gradient-1);
334
+ border-radius: 16px;
335
+ display: flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ margin: 0 auto 1rem;
339
+ font-size: 1.5rem;
340
+ }
341
+
342
+ .feature-card:nth-child(2) .feature-icon { background: var(--gradient-2); }
343
+ .feature-card:nth-child(3) .feature-icon { background: var(--gradient-3); }
344
+
345
+ .feature-title {
346
+ font-size: 1.125rem;
347
+ font-weight: 600;
348
+ margin-bottom: 0.5rem;
349
+ }
350
+
351
+ .feature-desc {
352
+ color: var(--text-secondary);
353
+ font-size: 0.9rem;
354
+ line-height: 1.5;
355
+ }
356
+
357
+ /* Footer */
358
+ footer {
359
+ text-align: center;
360
+ padding: 3rem 2rem;
361
+ color: var(--text-secondary);
362
+ font-size: 0.875rem;
363
+ }
364
+
365
+ footer a {
366
+ color: var(--primary-color);
367
+ text-decoration: none;
368
+ }
369
+
370
+ footer a:hover {
371
+ text-decoration: underline;
372
+ }
373
+
374
+ /* Video controls styling */
375
+ video::-webkit-media-controls-panel {
376
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
377
+ }
378
+
379
+ /* Responsive adjustments */
380
+ @media (max-width: 640px) {
381
+ h1 {
382
+ font-size: 2.5rem;
383
+ }
384
+
385
+ .subtitle {
386
+ font-size: 1rem;
387
+ }
388
+
389
+ .demo-grid {
390
+ gap: 1.5rem;
391
+ }
392
+
393
+ main {
394
+ padding: 1rem;
395
+ }
396
+ }
397
+ </style>
398
+ </head>
399
+ <body>
400
+ <div class="bg-animation"></div>
401
+
402
+ <header>
403
+ <div class="logo-container">
404
+ <img src="OpenApps_OpenEnv_RL.png" alt="OpenApp Logo" class="logo-img">
405
+ </div>
406
+ <h1>OpenApp + OpenEnv</h1>
407
+ <p class="subtitle">
408
+ A powerful integration bringing realistic web application environments to reinforcement learning agents.
409
+ Watch AI agents interact with calendar, messaging, code editor, and task management apps.
410
+ </p>
411
+ <div class="badge-container">
412
+ <span class="badge">
413
+ <svg class="badge-icon" viewBox="0 0 24 24" fill="currentColor">
414
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
415
+ </svg>
416
+ BrowserGym Compatible
417
+ </span>
418
+ <span class="badge">
419
+ <svg class="badge-icon" viewBox="0 0 24 24" fill="currentColor">
420
+ <path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/>
421
+ </svg>
422
+ 5+ Web Apps
423
+ </span>
424
+ <span class="badge">
425
+ <svg class="badge-icon" viewBox="0 0 24 24" fill="currentColor">
426
+ <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
427
+ </svg>
428
+ RL Ready
429
+ </span>
430
+ </div>
431
+ </header>
432
+
433
+ <main>
434
+ <div class="section-title">
435
+ <h2>🎬 Demo Showcase</h2>
436
+ <p>Watch our AI agents perform real tasks in realistic web environments</p>
437
+ </div>
438
+
439
+ <div class="demo-grid">
440
+ <!-- Messenger Demo -->
441
+ <div class="demo-card">
442
+ <div class="video-container">
443
+ <video id="video1" controls preload="metadata" poster="">
444
+ <source src="01-messages.mov" type="video/quicktime">
445
+ <source src="01-messages.mov" type="video/mp4">
446
+ Your browser does not support the video tag.
447
+ </video>
448
+ <div class="play-overlay" onclick="playVideo('video1', this)">
449
+ <div class="play-button">
450
+ <svg viewBox="0 0 24 24">
451
+ <path d="M8 5v14l11-7z"/>
452
+ </svg>
453
+ </div>
454
+ </div>
455
+ </div>
456
+ <div class="demo-info">
457
+ <div class="demo-header">
458
+ <div class="demo-icon">💬</div>
459
+ <h3 class="demo-title">Messenger App</h3>
460
+ </div>
461
+ <p class="demo-description">
462
+ AI agent navigates conversations, types messages interactively, and sends them to contacts.
463
+ Demonstrates natural language input and real-time chat interactions.
464
+ </p>
465
+ </div>
466
+ </div>
467
+
468
+ <!-- Code Editor Demo -->
469
+ <div class="demo-card">
470
+ <div class="video-container">
471
+ <video id="video2" controls preload="metadata">
472
+ <source src="02-editor.mov" type="video/quicktime">
473
+ <source src="02-editor.mov" type="video/mp4">
474
+ Your browser does not support the video tag.
475
+ </video>
476
+ <div class="play-overlay" onclick="playVideo('video2', this)">
477
+ <div class="play-button">
478
+ <svg viewBox="0 0 24 24">
479
+ <path d="M8 5v14l11-7z"/>
480
+ </svg>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ <div class="demo-info">
485
+ <div class="demo-header">
486
+ <div class="demo-icon">💻</div>
487
+ <h3 class="demo-title">Code Editor</h3>
488
+ </div>
489
+ <p class="demo-description">
490
+ Agent creates files and writes a complete PyTorch training loop with syntax highlighting.
491
+ Shows code generation, file management, and save operations.
492
+ </p>
493
+ </div>
494
+ </div>
495
+
496
+ <!-- Calendar Demo -->
497
+ <div class="demo-card">
498
+ <div class="video-container">
499
+ <video id="video3" controls preload="metadata">
500
+ <source src="03-calendar.mov" type="video/quicktime">
501
+ <source src="03-calendar.mov" type="video/mp4">
502
+ Your browser does not support the video tag.
503
+ </video>
504
+ <div class="play-overlay" onclick="playVideo('video3', this)">
505
+ <div class="play-button">
506
+ <svg viewBox="0 0 24 24">
507
+ <path d="M8 5v14l11-7z"/>
508
+ </svg>
509
+ </div>
510
+ </div>
511
+ </div>
512
+ <div class="demo-info">
513
+ <div class="demo-header">
514
+ <div class="demo-icon">📅</div>
515
+ <h3 class="demo-title">Calendar App</h3>
516
+ </div>
517
+ <p class="demo-description">
518
+ Navigate between calendar and agenda views, browse events across months,
519
+ and view detailed event information. Perfect for scheduling tasks.
520
+ </p>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Todo Demo -->
525
+ <div class="demo-card">
526
+ <div class="video-container">
527
+ <video id="video4" controls preload="metadata">
528
+ <source src="04-todo.mov" type="video/quicktime">
529
+ <source src="04-todo.mov" type="video/mp4">
530
+ Your browser does not support the video tag.
531
+ </video>
532
+ <div class="play-overlay" onclick="playVideo('video4', this)">
533
+ <div class="play-button">
534
+ <svg viewBox="0 0 24 24">
535
+ <path d="M8 5v14l11-7z"/>
536
+ </svg>
537
+ </div>
538
+ </div>
539
+ </div>
540
+ <div class="demo-info">
541
+ <div class="demo-header">
542
+ <div class="demo-icon">✅</div>
543
+ <h3 class="demo-title">Todo Manager</h3>
544
+ </div>
545
+ <p class="demo-description">
546
+ Browse and manage task lists, edit task details, mark items complete,
547
+ and organize priorities. Demonstrates CRUD operations on structured data.
548
+ </p>
549
+ </div>
550
+ </div>
551
+ </div>
552
+
553
+ <!-- Features Section -->
554
+ <div class="features-section">
555
+ <div class="section-title">
556
+ <h2>✨ Key Features</h2>
557
+ <p>Why OpenApp + OpenEnv is perfect for AI agent research</p>
558
+ </div>
559
+ <div class="features-grid">
560
+ <div class="feature-card">
561
+ <div class="feature-icon">🎮</div>
562
+ <h4 class="feature-title">Gymnasium Compatible</h4>
563
+ <p class="feature-desc">
564
+ Standard RL interface with observations, actions, and rewards.
565
+ Drop-in replacement for existing training pipelines.
566
+ </p>
567
+ </div>
568
+ <div class="feature-card">
569
+ <div class="feature-icon">🌐</div>
570
+ <h4 class="feature-title">Real Web Apps</h4>
571
+ <p class="feature-desc">
572
+ Authentic web applications with HTML, CSS, and JavaScript.
573
+ No simplified simulations – real browser interactions.
574
+ </p>
575
+ </div>
576
+ <div class="feature-card">
577
+ <div class="feature-icon">🔄</div>
578
+ <h4 class="feature-title">Configurable Tasks</h4>
579
+ <p class="feature-desc">
580
+ YAML-based configuration for custom scenarios, data, and rewards.
581
+ Easily create new training environments.
582
+ </p>
583
+ </div>
584
+ </div>
585
+ </div>
586
+ </main>
587
+
588
+ <footer>
589
+ <p>
590
+ Built for the <strong>OpenEnv Hackathon</strong> 🏆<br>
591
+ <a href="https://github.com/anthropics/anthropic-cookbook/tree/main/misc/openenv" target="_blank">OpenEnv Framework</a> •
592
+ <a href="https://github.com/ServiceNow/BrowserGym" target="_blank">BrowserGym</a>
593
+ </p>
594
+ </footer>
595
+
596
+ <script>
597
+ function playVideo(videoId, overlay) {
598
+ const video = document.getElementById(videoId);
599
+ video.play();
600
+ overlay.classList.add('hidden');
601
+ }
602
+
603
+ // Show overlay again when video ends or is paused
604
+ document.querySelectorAll('video').forEach(video => {
605
+ video.addEventListener('ended', function() {
606
+ const overlay = this.parentElement.querySelector('.play-overlay');
607
+ overlay.classList.remove('hidden');
608
+ });
609
+ });
610
+
611
+ // Add intersection observer for lazy loading
612
+ const videos = document.querySelectorAll('video');
613
+ const observer = new IntersectionObserver((entries) => {
614
+ entries.forEach(entry => {
615
+ if (entry.isIntersecting) {
616
+ entry.target.load();
617
+ }
618
+ });
619
+ }, { threshold: 0.25 });
620
+
621
+ videos.forEach(video => observer.observe(video));
622
+ </script>
623
+ </body>
624
+ </html>
envs/openapp_env/assets/openapps-demo.gif ADDED

Git LFS Details

  • SHA256: 906ff418bc943999178e1e1e962fb6f7a78d7026429adcfcc624b7f7f18eb0f3
  • Pointer size: 132 Bytes
  • Size of remote file: 3.13 MB
envs/openapp_env/client.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ OpenApp Environment HTTP Client.
9
+
10
+ This module provides the client for connecting to an OpenApp Environment server
11
+ over HTTP.
12
+ """
13
+
14
+ from typing import Any, Dict
15
+
16
+ # Support both in-repo and standalone imports
17
+ try:
18
+ # In-repo imports (when running from OpenEnv repository)
19
+ from openenv.core.client_types import StepResult
20
+ from openenv.core.env_server.types import State
21
+ from openenv.core.env_client import EnvClient
22
+ from .models import OpenAppAction, OpenAppObservation
23
+ except ImportError:
24
+ # Standalone imports (when environment is standalone with openenv-core from pip)
25
+ from openenv.core.client_types import StepResult
26
+ from openenv.core.env_server.types import State
27
+ from openenv.core.env_client import EnvClient
28
+ from openapp_env.models import OpenAppAction, OpenAppObservation
29
+
30
+
31
+ class OpenAppEnv(EnvClient[OpenAppAction, OpenAppObservation, State]):
32
+ """
33
+ HTTP client for the OpenApp Environment.
34
+
35
+ This client connects to an OpenAppEnvironment HTTP server and provides
36
+ methods to interact with it: reset(), step(), and state access.
37
+
38
+ The OpenApp environment simulates web applications (calendar, todo, messenger, maps)
39
+ and allows agents to interact with them using browser-based actions.
40
+
41
+ Example:
42
+ >>> # Connect to a running server
43
+ >>> client = OpenAppEnv(base_url="http://localhost:8000")
44
+ >>> result = client.reset()
45
+ >>> print(result.observation.url)
46
+ >>>
47
+ >>> # Click on an element
48
+ >>> result = client.step(OpenAppAction(action_type="click", bid="123"))
49
+ >>> print(result.observation.html)
50
+ >>> print(result.reward)
51
+
52
+ Example with Docker:
53
+ >>> # Automatically start container and connect
54
+ >>> client = OpenAppEnv.from_docker_image("openapp-env:latest")
55
+ >>> result = client.reset()
56
+ >>> # Fill a text field
57
+ >>> result = client.step(OpenAppAction(
58
+ ... action_type="fill",
59
+ ... bid="456",
60
+ ... text="Meeting with team"
61
+ ... ))
62
+ """
63
+
64
+ def _step_payload(self, action: OpenAppAction) -> Dict:
65
+ """
66
+ Convert OpenAppAction to JSON payload for step request.
67
+
68
+ Args:
69
+ action: OpenAppAction instance
70
+
71
+ Returns:
72
+ Dictionary representation suitable for JSON encoding
73
+ """
74
+ payload = {
75
+ "action_type": action.action_type,
76
+ }
77
+
78
+ # Add optional fields if present
79
+ if action.bid is not None:
80
+ payload["bid"] = action.bid
81
+ if action.text is not None:
82
+ payload["text"] = action.text
83
+ if action.value is not None:
84
+ payload["value"] = action.value
85
+ if action.url is not None:
86
+ payload["url"] = action.url
87
+ if action.direction is not None:
88
+ payload["direction"] = action.direction
89
+ if action.metadata:
90
+ payload["metadata"] = action.metadata
91
+
92
+ return payload
93
+
94
+ def _parse_result(self, payload: Dict) -> StepResult[OpenAppObservation]:
95
+ """
96
+ Parse server response into StepResult[OpenAppObservation].
97
+
98
+ Args:
99
+ payload: JSON response from server
100
+
101
+ Returns:
102
+ StepResult with OpenAppObservation
103
+ """
104
+ obs_data = payload.get("observation", {})
105
+ observation = OpenAppObservation(
106
+ html=obs_data.get("html", ""),
107
+ url=obs_data.get("url", ""),
108
+ open_pages_urls=obs_data.get("open_pages_urls", []),
109
+ active_page_index=obs_data.get("active_page_index", 0),
110
+ screenshot=obs_data.get("screenshot"),
111
+ axtree_txt=obs_data.get("axtree_txt", ""),
112
+ app_state=obs_data.get("app_state", {}),
113
+ task_info=obs_data.get("task_info"),
114
+ last_action_error=obs_data.get("last_action_error"),
115
+ done=payload.get("done", False),
116
+ reward=payload.get("reward"),
117
+ metadata=obs_data.get("metadata", {}),
118
+ )
119
+
120
+ return StepResult(
121
+ observation=observation,
122
+ reward=payload.get("reward"),
123
+ done=payload.get("done", False),
124
+ )
125
+
126
+ def _parse_state(self, payload: Dict) -> State:
127
+ """
128
+ Parse server response into State object.
129
+
130
+ Args:
131
+ payload: JSON response from /state endpoint
132
+
133
+ Returns:
134
+ State object with episode_id and step_count
135
+ """
136
+ return State(
137
+ episode_id=payload.get("episode_id"),
138
+ step_count=payload.get("step_count", 0),
139
+ )
envs/openapp_env/example_usage.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ # All rights reserved.
4
+ #
5
+ # This source code is licensed under the BSD-style license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ """
9
+ Example usage of OpenApp Environment.
10
+
11
+ This script demonstrates how to use the OpenApp environment with OpenEnv.
12
+
13
+ For a complete runnable example, see: examples/openapp_example.py
14
+
15
+ Visualization Options:
16
+ To see the browser window and watch agent interactions:
17
+
18
+ Terminal 1: Start OpenApps server with visible browser
19
+ cd OpenApps
20
+ python OpenApps/launch.py browsergym_env_args.headless=False
21
+
22
+ Terminal 2: Run your agent code
23
+ export OPENAPPS_URL=http://localhost:5001
24
+ python examples/openapp_example.py --mode local
25
+
26
+ Or access OpenApps web interface at http://localhost:5001
27
+ Docker mode web interface at http://localhost:8000/web
28
+
29
+ Important:
30
+ Browser visualization is controlled by the OpenApps SERVER, not the client.
31
+ Launch the server with 'browsergym_env_args.headless=False' to see the browser.
32
+ """
33
+
34
+ import sys
35
+ from pathlib import Path
36
+
37
+ # Add src to path for local testing
38
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
39
+
40
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
41
+
42
+
43
+ def example_basic_usage():
44
+ """Basic usage example."""
45
+ print("=" * 60)
46
+ print("OpenApp Environment - Basic Usage Example")
47
+ print("=" * 60)
48
+
49
+ # Option 1: Connect to a running server
50
+ print("\nOption 1: Connect to running server")
51
+ print("client = OpenAppEnv(base_url='http://localhost:8000')")
52
+
53
+ # Option 2: Start from Docker image (recommended)
54
+ print("\nOption 2: Start from Docker image")
55
+ print("client = OpenAppEnv.from_docker_image('openapp-env:latest')")
56
+
57
+ print("\n" + "-" * 60)
58
+
59
+
60
+ def example_actions():
61
+ """Example of different action types."""
62
+ print("\nExample Actions")
63
+ print("-" * 60)
64
+
65
+ # Navigate to a page
66
+ print("\n1. Navigate to calendar app:")
67
+ print("action = OpenAppAction(")
68
+ print(" action_type='goto',")
69
+ print(" url='http://localhost:5001/calendar'")
70
+ print(")")
71
+ print("result = client.step(action)")
72
+
73
+ # Click on an element
74
+ print("\n2. Click on a button:")
75
+ print("action = OpenAppAction(")
76
+ print(" action_type='click',")
77
+ print(" bid='add-event-btn' # BrowserGym element ID")
78
+ print(")")
79
+ print("result = client.step(action)")
80
+
81
+ # Fill a form field
82
+ print("\n3. Fill in text input:")
83
+ print("action = OpenAppAction(")
84
+ print(" action_type='fill',")
85
+ print(" bid='event-title-input',")
86
+ print(" text='Team Meeting'")
87
+ print(")")
88
+ print("result = client.step(action)")
89
+
90
+ # Select from dropdown
91
+ print("\n4. Select from dropdown:")
92
+ print("action = OpenAppAction(")
93
+ print(" action_type='select_option',")
94
+ print(" bid='time-select',")
95
+ print(" value='14:00'")
96
+ print(")")
97
+ print("result = client.step(action)")
98
+
99
+ # Scroll the page
100
+ print("\n5. Scroll down:")
101
+ print("action = OpenAppAction(")
102
+ print(" action_type='scroll',")
103
+ print(" direction='down'")
104
+ print(")")
105
+ print("result = client.step(action)")
106
+
107
+ # No operation
108
+ print("\n6. No operation (useful for observation):")
109
+ print("action = OpenAppAction(action_type='noop')")
110
+ print("result = client.step(action)")
111
+
112
+
113
+ def example_observations():
114
+ """Example of observation structure."""
115
+ print("\n\nObservation Structure")
116
+ print("-" * 60)
117
+
118
+ print("\nAfter reset() or step(), you receive:")
119
+ print("result.observation.html # Current page HTML")
120
+ print("result.observation.url # Current URL")
121
+ print("result.observation.open_pages_urls # All open pages")
122
+ print("result.observation.axtree_txt # Accessibility tree")
123
+ print("result.observation.app_state # App states (calendar, todo, etc.)")
124
+ print("result.observation.task_info # Task information (if using tasks)")
125
+ print("result.observation.screenshot # Page screenshot (base64)")
126
+ print("result.observation.last_action_error # Error from last action")
127
+ print("result.reward # Step reward")
128
+ print("result.done # Episode done flag")
129
+
130
+
131
+ def example_complete_workflow():
132
+ """Complete workflow example."""
133
+ print("\n\nComplete Workflow Example")
134
+ print("=" * 60)
135
+
136
+ example_code = """
137
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
138
+
139
+ # Create client (starts Docker container)
140
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
141
+
142
+ try:
143
+ # Reset environment
144
+ result = client.reset()
145
+ print(f"Starting at: {result.observation.url}")
146
+
147
+ # Navigate to calendar
148
+ result = client.step(OpenAppAction(
149
+ action_type="goto",
150
+ url="http://localhost:5001/calendar"
151
+ ))
152
+
153
+ # Click to add new event
154
+ result = client.step(OpenAppAction(
155
+ action_type="click",
156
+ bid="new-event-button"
157
+ ))
158
+
159
+ # Fill event title
160
+ result = client.step(OpenAppAction(
161
+ action_type="fill",
162
+ bid="title-input",
163
+ text="Project Review Meeting"
164
+ ))
165
+
166
+ # Fill event date
167
+ result = client.step(OpenAppAction(
168
+ action_type="fill",
169
+ bid="date-input",
170
+ text="2025-12-15"
171
+ ))
172
+
173
+ # Submit form
174
+ result = client.step(OpenAppAction(
175
+ action_type="click",
176
+ bid="submit-button"
177
+ ))
178
+
179
+ print(f"Reward: {result.reward}")
180
+ print(f"Done: {result.done}")
181
+ print(f"App State: {result.observation.app_state}")
182
+
183
+ finally:
184
+ # Always cleanup
185
+ client.close()
186
+ """
187
+
188
+ print(example_code)
189
+
190
+
191
+ def example_with_tasks():
192
+ """Example using OpenApps tasks."""
193
+ print("\n\nUsing Tasks (Task-Based RL)")
194
+ print("=" * 60)
195
+
196
+ example_code = """
197
+ # Environment can be configured with specific tasks
198
+ # Tasks define goals and automatic reward calculation
199
+
200
+ from envs.openapp_env.server.openapp_environment import OpenAppEnvironment
201
+
202
+ env = OpenAppEnvironment(
203
+ openapps_url="http://localhost:5001", # OpenApps server URL
204
+ task_name="add_meeting_with_dennis", # Optional task name
205
+ headless=False, # Set to False to watch the browser
206
+ max_steps=50,
207
+ )
208
+
209
+ obs = env.reset()
210
+ # Now the environment has a goal: add a meeting with Dennis
211
+ # Rewards will be based on progress toward this goal
212
+
213
+ # Agent loop
214
+ done = False
215
+ while not done:
216
+ action = agent.get_action(obs) # Your agent
217
+ obs = env.step(action)
218
+ done = obs.done
219
+
220
+ print(f"Task completed! Reward: {obs.reward}")
221
+ env.close()
222
+ """
223
+
224
+ print(example_code)
225
+
226
+
227
+ def example_visualization():
228
+ """Example of visualization options."""
229
+ print("\n\nVisualization Options")
230
+ print("=" * 60)
231
+
232
+ example_code = """
233
+ # Option 1: Show browser window (watch agent in real-time)
234
+ from envs.openapp_env.server.openapp_environment import OpenAppEnvironment
235
+
236
+ env = OpenAppEnvironment(
237
+ openapps_url="http://localhost:5001",
238
+ headless=False, # Show browser window
239
+ )
240
+
241
+ obs = env.reset()
242
+ # You'll see a browser window open!
243
+
244
+ # Option 2: Access web interface manually
245
+ # While OpenApps server is running, open in your browser:
246
+ # - Main: http://localhost:5001
247
+ # - Calendar: http://localhost:5001/calendar
248
+ # - Todo: http://localhost:5001/todo
249
+ # - Messenger: http://localhost:5001/messenger
250
+ # - Maps: http://localhost:5001/maps
251
+
252
+ # Option 3: Use the example script with --show-browser
253
+ # python examples/openapp_example.py --mode local --show-browser
254
+ """
255
+
256
+ print(example_code)
257
+
258
+
259
+ def main():
260
+ """Run all examples."""
261
+ example_basic_usage()
262
+ example_actions()
263
+ example_observations()
264
+ example_complete_workflow()
265
+ example_with_tasks()
266
+ example_visualization()
267
+
268
+ print("\n" + "=" * 60)
269
+ print("For a complete runnable example:")
270
+ print(" python examples/openapp_example.py --mode local --show-browser")
271
+ print("\nFor more information, see:")
272
+ print("- README.md in this directory")
273
+ print("- OpenApps docs: https://facebookresearch.github.io/OpenApps/")
274
+ print("- OpenEnv docs: https://meta-pytorch.org/OpenEnv/")
275
+ print("=" * 60)
276
+
277
+
278
+ if __name__ == "__main__":
279
+ main()
envs/openapp_env/models.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ Data models for the OpenApp Environment.
9
+
10
+ The OpenApp environment provides a simulated web application environment
11
+ for training and evaluating UI agents that interact with various apps
12
+ (calendar, todo, messenger, maps, etc.) using browser actions.
13
+ """
14
+
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from pydantic import Field
18
+
19
+ # Support both in-repo and standalone imports
20
+ try:
21
+ # In-repo imports (when running from OpenEnv repository)
22
+ from openenv.core.env_server.types import Action, Observation
23
+ except ImportError:
24
+ # Standalone imports (when environment is standalone with openenv-core from pip)
25
+ from openenv.core.env_server.types import Action, Observation
26
+
27
+
28
+ class OpenAppAction(Action):
29
+ """
30
+ Action for the OpenApp environment.
31
+
32
+ Supports BrowserGym-style actions for web interaction:
33
+ - click: Click on an element (requires bid - BrowserGym ID)
34
+ - fill: Fill a text field (requires bid and text)
35
+ - select_option: Select from dropdown (requires bid and value)
36
+ - goto: Navigate to URL (requires url)
37
+ - scroll: Scroll the page (requires direction)
38
+ - send_keys: Send keyboard input (requires text)
39
+ - noop: No operation
40
+
41
+ Attributes:
42
+ action_type: Type of action to perform
43
+ bid: BrowserGym element ID (for click, fill, select_option)
44
+ text: Text content (for fill, send_keys)
45
+ value: Value to select (for select_option)
46
+ url: URL to navigate to (for goto)
47
+ direction: Scroll direction - 'up' or 'down' (for scroll)
48
+ """
49
+
50
+ action_type: str = Field(
51
+ ..., description="Type of action: click, fill, select_option, goto, scroll, send_keys, noop"
52
+ )
53
+ bid: Optional[str] = Field(default=None, description="BrowserGym element ID")
54
+ text: Optional[str] = Field(default=None, description="Text content for fill or send_keys")
55
+ value: Optional[str] = Field(default=None, description="Value for select_option")
56
+ url: Optional[str] = Field(default=None, description="URL for goto action")
57
+ direction: Optional[str] = Field(default=None, description="Scroll direction: 'up' or 'down'")
58
+
59
+
60
+ class OpenAppObservation(Observation):
61
+ """
62
+ Observation from the OpenApp environment.
63
+
64
+ Provides comprehensive state information about the web apps and browser state.
65
+
66
+ Attributes:
67
+ html: Current page HTML content
68
+ url: Current page URL
69
+ open_pages_urls: List of all open page URLs
70
+ active_page_index: Index of currently active page
71
+ screenshot: Base64-encoded screenshot (optional)
72
+ axtree_txt: Accessibility tree as text (for element interaction)
73
+ app_state: Current state of all apps (calendar, todo, messenger, map)
74
+ task_info: Information about the current task (if any)
75
+ last_action_error: Error message from last action (if failed)
76
+ """
77
+
78
+ html: str = Field(default="", description="Current page HTML content")
79
+ url: str = Field(default="", description="Current page URL")
80
+ open_pages_urls: List[str] = Field(default_factory=list, description="List of all open page URLs")
81
+ active_page_index: int = Field(default=0, ge=0, description="Index of currently active page")
82
+ screenshot: Optional[str] = Field(default=None, description="Base64-encoded screenshot")
83
+ axtree_txt: str = Field(default="", description="Accessibility tree as text")
84
+ app_state: Dict[str, Any] = Field(default_factory=dict, description="State of all apps")
85
+ task_info: Optional[Dict[str, Any]] = Field(default=None, description="Current task information")
86
+ last_action_error: Optional[str] = Field(default=None, description="Error from last action")
envs/openapp_env/openenv.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: openapp_env
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
envs/openapp_env/pyproject.toml ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ [build-system]
8
+ requires = ["setuptools>=45", "wheel"]
9
+ build-backend = "setuptools.build_meta"
10
+
11
+ [project]
12
+ name = "openenv-openapp_env"
13
+ version = "0.1.0"
14
+ description = "OpenApp Environment for OpenEnv - web application simulation environment for UI agents"
15
+ requires-python = ">=3.11,<3.14"
16
+ dependencies = [
17
+ # NOTE: openenv-core is NOT listed here to avoid openai version conflict
18
+ # It is installed separately in the Dockerfile with --no-deps to avoid
19
+ # openai>=2.7.2 conflicting with OpenApps' openai<2 requirement.
20
+ # For local development, install manually:
21
+ # pip install --no-deps "openenv-core @ git+https://github.com/meta-pytorch/OpenEnv.git"
22
+ # pip install fastapi pydantic uvicorn requests websockets
23
+ #
24
+ # NOTE: open_apps is also NOT listed here for the same reason.
25
+ # Install manually for local development:
26
+ # pip install git+https://github.com/facebookresearch/OpenApps.git
27
+ #
28
+ # Server dependencies (these are installed by Dockerfile separately for openenv-core)
29
+ "fastapi>=0.115.0",
30
+ "pydantic>=2.0.0",
31
+ "uvicorn[standard]>=0.24.0",
32
+ "requests>=2.31.0",
33
+ "websockets>=15.0.1",
34
+ # BrowserGym dependencies
35
+ "browsergym>=0.13.3",
36
+ "playwright>=1.40.0",
37
+ # Additional dependencies for web app interaction
38
+ "python-multipart>=0.0.20",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "pytest>=8.0.0",
44
+ "pytest-cov>=4.0.0",
45
+ ]
46
+
47
+ [project.scripts]
48
+ server = "openapp_env.server.app:main"
49
+
50
+ [tool.setuptools]
51
+ packages = ["openapp_env", "openapp_env.server"]
52
+ package-dir = { "openapp_env" = ".", "openapp_env.server" = "server" }
53
+
54
+ [tool.setuptools.package-data]
55
+ openapp_env = ["**/*.yaml", "**/*.yml", "**/*.md"]
56
+
57
+ [tool.hatch.metadata]
58
+ allow-direct-references = true
envs/openapp_env/server/Dockerfile ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ # Dockerfile for OpenApp Environment
8
+ # This image provides OpenApps web application simulation for UI agent training
9
+ #
10
+ # This Dockerfile works for both local builds and HuggingFace Spaces deployment:
11
+ # - Local build: cd envs/openapp_env && docker build -t openapp-env:latest -f server/Dockerfile .
12
+ # - HuggingFace: Automatically deployed via `openenv push`
13
+ #
14
+ # Run with web interface:
15
+ # docker run -p 8000:8000 -e ENABLE_WEB_INTERFACE=true openapp-env:latest
16
+
17
+ FROM python:3.11-slim
18
+
19
+ # Set metadata
20
+ LABEL maintainer="OpenEnv Team"
21
+ LABEL description="OpenApp Environment with BrowserGym for UI agent training"
22
+ LABEL org.opencontainers.image.source="https://github.com/meta-pytorch/OpenEnv"
23
+
24
+ # Set working directory
25
+ WORKDIR /app/env
26
+
27
+ # Install system dependencies
28
+ # - git: required to clone OpenApps from GitHub
29
+ # - curl: for healthcheck
30
+ # - Playwright/BrowserGym dependencies: fonts, libraries for browser automation
31
+ RUN apt-get update && \
32
+ apt-get install -y --no-install-recommends \
33
+ git \
34
+ curl \
35
+ ca-certificates \
36
+ wget \
37
+ gnupg \
38
+ # Playwright/Chromium dependencies
39
+ libnss3 \
40
+ libnspr4 \
41
+ libatk1.0-0 \
42
+ libatk-bridge2.0-0 \
43
+ libcups2 \
44
+ libdrm2 \
45
+ libdbus-1-3 \
46
+ libxkbcommon0 \
47
+ libxcomposite1 \
48
+ libxdamage1 \
49
+ libxfixes3 \
50
+ libxrandr2 \
51
+ libgbm1 \
52
+ libasound2 \
53
+ libpango-1.0-0 \
54
+ libcairo2 \
55
+ libatspi2.0-0 \
56
+ libxshmfence1 \
57
+ fonts-liberation \
58
+ libappindicator3-1 \
59
+ xdg-utils && \
60
+ rm -rf /var/lib/apt/lists/*
61
+
62
+ # Set environment variables
63
+ ENV PYTHONUNBUFFERED=1
64
+
65
+ # Set working directory
66
+ WORKDIR /app/env
67
+
68
+ # Copy environment files
69
+ # Context is always the env directory (envs/openapp_env/)
70
+ # - GitHub Actions: uses context: envs/openapp_env
71
+ # - HuggingFace: openenv push uploads env dir as context
72
+ COPY . /app/env
73
+
74
+ # Install OpenApps FIRST to establish openai<2 (required by agentlab)
75
+ # This must happen before openenv-core to avoid version conflict
76
+ WORKDIR /app
77
+ RUN git clone https://github.com/facebookresearch/OpenApps.git openapps && \
78
+ cd openapps && \
79
+ pip install --no-cache-dir -e .
80
+
81
+ # Verify OpenApps installation
82
+ RUN python -c "import open_apps; print('✓ OpenApps installed')"
83
+
84
+ # Install openenv-core from GitHub with --no-deps to avoid openai>=2.7.2 conflict
85
+ # Then install only the server dependencies (no openai needed for server)
86
+ RUN pip install --no-cache-dir --no-deps "openenv-core[core]>=0.2.1" && \
87
+ pip install --no-cache-dir fastapi pydantic uvicorn requests websockets
88
+
89
+ # Install openapp_env and remaining dependencies
90
+ WORKDIR /app/env
91
+ RUN pip install --no-cache-dir -e .
92
+
93
+ # Verify installation
94
+ RUN python -c "import openapp_env; print('✓ openapp_env installed')" && \
95
+ python -c "import openapp_env.server.app; print('✓ openapp_env.server.app importable')"
96
+
97
+ # Install Playwright browsers (Chromium for BrowserGym)
98
+ # We already installed system dependencies above, so just install the browser
99
+ RUN playwright install chromium
100
+
101
+ # Copy startup script
102
+ WORKDIR /app/env
103
+ COPY server/start.sh /app/start.sh
104
+ RUN chmod +x /app/start.sh
105
+
106
+ # OpenApp-specific environment variables (can be overridden at runtime)
107
+ ENV OPENAPPS_URL=http://localhost:5001
108
+ ENV OPENAPPS_PORT=5001
109
+ ENV OPENAPP_HEADLESS=true
110
+ ENV OPENAPP_MAX_STEPS=50
111
+
112
+ # Hydra requires USER environment variable
113
+ ENV USER=root
114
+
115
+ # Enable web interface by default (set to false to disable)
116
+ ENV ENABLE_WEB_INTERFACE=true
117
+
118
+ # Expose ports (8000 for FastAPI, 5001 for OpenApps)
119
+ EXPOSE 8000 5001
120
+
121
+ # Health check
122
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
123
+ CMD curl -f http://localhost:8000/health || exit 1
124
+
125
+ # Run the startup script that launches both OpenApps server and FastAPI server
126
+ # Web interface will be available at /web if ENABLE_WEB_INTERFACE=true
127
+ # API documentation available at /docs
128
+ CMD ["/app/start.sh"]
envs/openapp_env/server/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """OpenApp Environment Server."""
envs/openapp_env/server/app.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ FastAPI application for the OpenApp Environment.
9
+
10
+ This module creates an HTTP server that exposes the OpenAppEnvironment
11
+ over HTTP endpoints, making it compatible with HTTPEnvClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from ..models import OpenAppAction, OpenAppObservation
29
+ from .openapp_environment import OpenAppEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv-core from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from openapp_env.models import OpenAppAction, OpenAppObservation
34
+ from openapp_env.server.openapp_environment import OpenAppEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ # Each client gets its own environment instance. The environment reads
39
+ # OPENAPPS_URL from environment variables in __init__.
40
+ app = create_app(OpenAppEnvironment, OpenAppAction, OpenAppObservation, env_name="openapp_env")
41
+
42
+
43
+ def main():
44
+ """
45
+ Entry point for direct execution via uv run or python -m.
46
+
47
+ This function enables running the server without Docker:
48
+ uv run --project . server
49
+ python -m envs.openapp_env.server.app
50
+ openenv serve openapp_env
51
+
52
+ """
53
+ import uvicorn
54
+
55
+ uvicorn.run(app, host="0.0.0.0", port=8000)
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
envs/openapp_env/server/openapp_environment.py ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ OpenApp Environment Implementation.
9
+
10
+ A web application simulation environment that wraps OpenApps and BrowserGym.
11
+ This environment provides agent interaction with simulated web apps including
12
+ calendar, todo, messenger, and maps applications.
13
+ """
14
+
15
+ import logging
16
+ import os
17
+ import subprocess
18
+ import time
19
+ import urllib.request
20
+ from pathlib import Path
21
+ from typing import Any, Dict, Optional, Tuple
22
+ from uuid import uuid4
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Support both in-repo and standalone imports
27
+ try:
28
+ # In-repo imports (when running from OpenEnv repository)
29
+ from openenv.core.env_server.interfaces import Environment
30
+ from openenv.core.env_server.types import State
31
+ from ..models import OpenAppAction, OpenAppObservation
32
+ except ImportError:
33
+ # Standalone imports (when environment is standalone with openenv-core from pip)
34
+ from openenv.core.env_server.interfaces import Environment
35
+ from openenv.core.env_server.types import State
36
+ from openapp_env.models import OpenAppAction, OpenAppObservation
37
+
38
+
39
+ class GenericOpenAppsTask:
40
+ """
41
+ A generic task for OpenApps interaction without specific goals.
42
+
43
+ This is a simple wrapper that allows BrowserGym to interact with OpenApps
44
+ without requiring a specific task. For task-based interaction, use the
45
+ OpenAppsTask from open_apps.tasks.add_tasks_to_browsergym.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ base_url: str,
51
+ seed: int = 1,
52
+ **kwargs,
53
+ ) -> None:
54
+ """
55
+ Initialize generic OpenApps task.
56
+
57
+ Args:
58
+ base_url: Base URL of the OpenApps server
59
+ seed: Random seed (required by BrowserGym)
60
+ **kwargs: Additional arguments (ignored)
61
+ """
62
+ try:
63
+ from browsergym.core.task import AbstractBrowserTask
64
+ import playwright.sync_api
65
+ except ImportError:
66
+ raise ImportError(
67
+ "BrowserGym is required. Install with: pip install browsergym"
68
+ )
69
+
70
+ # Store as instance attributes
71
+ self.base_url = base_url
72
+ self.seed = seed
73
+
74
+ # BrowserGym task properties
75
+ self.viewport = {"width": 1024, "height": 768}
76
+ self.slow_mo = 100
77
+ self.timeout = 5000
78
+
79
+ # Additional properties that BrowserGym might expect
80
+ self.locale = None
81
+ self.timezone_id = None
82
+ self.geolocation = None
83
+
84
+ def setup(
85
+ self, page: "playwright.sync_api.Page"
86
+ ) -> Tuple[str, Dict[str, Any]]:
87
+ """
88
+ Set up the task by navigating to the base URL.
89
+
90
+ Args:
91
+ page: Playwright page object
92
+
93
+ Returns:
94
+ Tuple of (goal_string, info_dict)
95
+ """
96
+ page.goto(self.base_url)
97
+ return "Explore OpenApps", {}
98
+
99
+ def teardown(self) -> None:
100
+ """Clean up after task completion."""
101
+ pass
102
+
103
+ def validate(
104
+ self, page: "playwright.sync_api.Page", chat_messages: list[str]
105
+ ) -> Tuple[float, bool, str, Dict[str, Any]]:
106
+ """
107
+ Validate task state and return reward.
108
+
109
+ Args:
110
+ page: Playwright page object
111
+ chat_messages: List of chat messages
112
+
113
+ Returns:
114
+ Tuple of (reward, done, message, info)
115
+ """
116
+ # Generic task never completes automatically
117
+ return 0.0, False, "", {}
118
+
119
+ def cheat(
120
+ self, page: "playwright.sync_api.Page", chat_messages: list[str]
121
+ ) -> None:
122
+ """Cheat method (no-op for generic task)."""
123
+ pass
124
+
125
+
126
+ class OpenAppEnvironment(Environment):
127
+ """
128
+ A web application environment that wraps OpenApps and BrowserGym.
129
+
130
+ This environment launches OpenApps web server and provides a BrowserGym-like
131
+ interface for agents to interact with simulated web applications.
132
+
133
+ Args:
134
+ openapps_path: Path to OpenApps directory (default: auto-detect)
135
+ web_app_port: Port for OpenApps web server (default: 5001)
136
+ headless: Run browser in headless mode (default: True)
137
+ task_name: Optional task name to evaluate (e.g., "add_meeting_with_dennis")
138
+ apps_config: Configuration for apps (default: all enabled)
139
+ max_steps: Maximum steps per episode (default: 50)
140
+
141
+ Example:
142
+ >>> env = OpenAppEnvironment()
143
+ >>> obs = env.reset()
144
+ >>> print(obs.url) # Starting page URL
145
+ >>>
146
+ >>> # Click on an element
147
+ >>> action = OpenAppAction(action_type="click", bid="calendar-btn")
148
+ >>> obs = env.step(action)
149
+ >>> print(obs.html)
150
+ """
151
+
152
+ def __init__(
153
+ self,
154
+ openapps_url: Optional[str] = None,
155
+ openapps_path: Optional[str] = None,
156
+ web_app_port: int = 5001,
157
+ headless: bool = True,
158
+ task_name: Optional[str] = None,
159
+ apps_config: Optional[Dict[str, Any]] = None,
160
+ max_steps: int = 50,
161
+ ):
162
+ """Initialize the OpenApp environment."""
163
+ self._state = State(episode_id=str(uuid4()), step_count=0)
164
+ self._max_steps = max_steps
165
+
166
+ # OpenApps configuration
167
+ # Priority: 1. openapps_url, 2. OPENAPPS_URL env var, 3. Try to find/launch
168
+ self.openapps_url = openapps_url or os.environ.get("OPENAPPS_URL")
169
+ if not self.openapps_url:
170
+ self.web_app_port = web_app_port
171
+ self.openapps_url = f"http://localhost:{web_app_port}"
172
+
173
+ self.openapps_path = openapps_path
174
+ self.headless = headless
175
+ self.task_name = task_name
176
+ self.apps_config = apps_config or {}
177
+
178
+ # Runtime state
179
+ self._apps_process: Optional[subprocess.Popen] = None
180
+ self._browser_env = None
181
+ self._current_html = ""
182
+ self._current_url = ""
183
+ self._current_axtree = ""
184
+ self._app_state = {}
185
+ self._last_action_error = None
186
+ self._episode_reward = 0.0
187
+
188
+ def _detect_openapps_path(self) -> str:
189
+ """
190
+ Auto-detect OpenApps path.
191
+
192
+ Since OpenApps is installed as a Python package, we use the installed
193
+ package location instead of requiring a separate directory.
194
+ """
195
+ # Check if user provided a custom path via environment variable
196
+ env_path = os.environ.get("OPENAPPS_PATH")
197
+ if env_path and Path(env_path).exists():
198
+ return env_path
199
+
200
+ # Try to find OpenApps as an installed package
201
+ try:
202
+ import open_apps
203
+
204
+ openapps_pkg_path = Path(open_apps.__file__).parent.parent
205
+ if openapps_pkg_path.exists():
206
+ return str(openapps_pkg_path)
207
+ except ImportError:
208
+ pass
209
+
210
+ raise ValueError(
211
+ "OpenApps not found. Please install it with: "
212
+ "pip install git+https://github.com/facebookresearch/OpenApps.git "
213
+ "or set OPENAPPS_PATH environment variable."
214
+ )
215
+
216
+ def _launch_openapps_server(self) -> Optional[subprocess.Popen]:
217
+ """
218
+ Launch OpenApps web server in background.
219
+
220
+ Returns None if server is expected to be already running (OPENAPPS_URL set).
221
+ """
222
+ # If OPENAPPS_URL is set, assume server is already running
223
+ if os.environ.get("OPENAPPS_URL"):
224
+ logger.info(f"Using existing OpenApps server at {self.openapps_url}")
225
+ # Wait for server to be available
226
+ self._wait_for_server(max_wait=5)
227
+ return None
228
+
229
+ # Otherwise, provide helpful error message
230
+ raise NotImplementedError(
231
+ "Automatic OpenApps server launch is not yet implemented.\n"
232
+ "\n"
233
+ "Please start OpenApps manually in a separate terminal:\n"
234
+ " 1. Clone OpenApps: git clone https://github.com/facebookresearch/OpenApps.git\n"
235
+ " 2. Install: cd OpenApps && uv sync\n"
236
+ " 3. Run: uv run launch.py\n"
237
+ "\n"
238
+ "Then set the OPENAPPS_URL environment variable:\n"
239
+ " export OPENAPPS_URL=http://localhost:5001\n"
240
+ "\n"
241
+ "Or use Docker mode which handles this automatically:\n"
242
+ " python examples/openapp_example.py --mode docker\n"
243
+ )
244
+
245
+ def _wait_for_server(self, max_wait: int = 30):
246
+ """Wait for OpenApps server to become available."""
247
+ for i in range(max_wait):
248
+ try:
249
+ response = urllib.request.urlopen(self.openapps_url, timeout=2)
250
+ if response.status == 200:
251
+ return
252
+ except Exception:
253
+ pass
254
+ time.sleep(1)
255
+
256
+ raise TimeoutError(f"OpenApps server did not start within {max_wait} seconds")
257
+
258
+ def _initialize_browser(self):
259
+ """Initialize BrowserGym environment for interaction."""
260
+ try:
261
+ from browsergym.core.env import BrowserEnv
262
+ except ImportError:
263
+ raise ImportError(
264
+ "BrowserGym is required for OpenApp environment. "
265
+ "Install it with: pip install browsergym"
266
+ )
267
+
268
+ # Create BrowserGym environment with generic OpenApps task
269
+ self._browser_env = BrowserEnv(
270
+ task_entrypoint=GenericOpenAppsTask,
271
+ task_kwargs={"base_url": self.openapps_url},
272
+ headless=self.headless,
273
+ slow_mo=200, # Slow down actions so they're visible (200ms delay)
274
+ )
275
+
276
+ def _get_current_observation(self) -> Dict[str, Any]:
277
+ """Extract current observation from browser state."""
278
+ if self._browser_env is None:
279
+ return {
280
+ "html": "",
281
+ "url": self.openapps_url,
282
+ "open_pages_urls": [self.openapps_url],
283
+ "active_page_index": 0,
284
+ "axtree_txt": "",
285
+ "app_state": {},
286
+ }
287
+
288
+ # Get browser state (implementation depends on BrowserGym API)
289
+ # This is a simplified version - actual implementation would use BrowserGym's observation
290
+ return {
291
+ "html": self._current_html,
292
+ "url": self._current_url,
293
+ "open_pages_urls": [self._current_url],
294
+ "active_page_index": 0,
295
+ "axtree_txt": self._current_axtree,
296
+ "app_state": self._app_state,
297
+ }
298
+
299
+ def reset(self) -> OpenAppObservation:
300
+ """
301
+ Reset the environment.
302
+
303
+ Returns:
304
+ OpenAppObservation with initial state
305
+ """
306
+ # Reset state
307
+ self._state = State(episode_id=str(uuid4()), step_count=0)
308
+ self._episode_reward = 0.0
309
+ self._last_action_error = None
310
+
311
+ # Check if OpenApps server is running, start if needed
312
+ if self._apps_process is None and not os.environ.get("OPENAPPS_URL"):
313
+ self._apps_process = self._launch_openapps_server()
314
+
315
+ # Initialize browser
316
+ if self._browser_env is None:
317
+ self._initialize_browser()
318
+
319
+ # Reset the BrowserGym environment
320
+ try:
321
+ obs, info = self._browser_env.reset()
322
+ # Extract observation data from BrowserGym
323
+ self._current_url = obs.get("url", self.openapps_url)
324
+ self._current_html = obs.get("dom_txt", "")
325
+ self._current_axtree = obs.get("axtree_txt", "")
326
+ self._app_state = {}
327
+ except Exception as e:
328
+ logger.warning(f"Failed to reset browser environment: {e}")
329
+ # Fallback to placeholder values
330
+ self._current_url = self.openapps_url
331
+ self._current_html = "<html><body>OpenApps Ready</body></html>"
332
+ self._current_axtree = ""
333
+ self._app_state = {}
334
+
335
+ obs_data = self._get_current_observation()
336
+
337
+ return OpenAppObservation(
338
+ html=obs_data["html"],
339
+ url=obs_data["url"],
340
+ open_pages_urls=obs_data["open_pages_urls"],
341
+ active_page_index=obs_data["active_page_index"],
342
+ axtree_txt=obs_data["axtree_txt"],
343
+ app_state=obs_data["app_state"],
344
+ task_info={"task_name": self.task_name} if self.task_name else None,
345
+ last_action_error=None,
346
+ done=False,
347
+ reward=0.0,
348
+ )
349
+
350
+ def step(self, action: OpenAppAction) -> OpenAppObservation: # type: ignore[override]
351
+ """
352
+ Execute a step in the environment.
353
+
354
+ Args:
355
+ action: OpenAppAction to execute
356
+
357
+ Returns:
358
+ OpenAppObservation with resulting state and reward
359
+ """
360
+ self._state.step_count += 1
361
+ self._last_action_error = None
362
+ reward = 0.0
363
+
364
+ try:
365
+ # Execute action based on type
366
+ if action.action_type == "click":
367
+ reward = self._execute_click(action.bid)
368
+ elif action.action_type == "fill":
369
+ reward = self._execute_fill(action.bid, action.text)
370
+ elif action.action_type == "select_option":
371
+ reward = self._execute_select(action.bid, action.value)
372
+ elif action.action_type == "goto":
373
+ reward = self._execute_goto(action.url)
374
+ elif action.action_type == "scroll":
375
+ reward = self._execute_scroll(action.direction)
376
+ elif action.action_type == "send_keys":
377
+ reward = self._execute_send_keys(action.text)
378
+ elif action.action_type == "noop":
379
+ reward = 0.0
380
+ else:
381
+ self._last_action_error = f"Unknown action type: {action.action_type}"
382
+ reward = -0.1
383
+
384
+ except Exception as e:
385
+ self._last_action_error = str(e)
386
+ reward = -0.1
387
+
388
+ # Update cumulative reward
389
+ self._episode_reward += reward
390
+
391
+ # Check if episode is done
392
+ done = self._state.step_count >= self._max_steps
393
+
394
+ # Get current observation
395
+ obs_data = self._get_current_observation()
396
+
397
+ return OpenAppObservation(
398
+ html=obs_data["html"],
399
+ url=obs_data["url"],
400
+ open_pages_urls=obs_data["open_pages_urls"],
401
+ active_page_index=obs_data["active_page_index"],
402
+ axtree_txt=obs_data["axtree_txt"],
403
+ app_state=obs_data["app_state"],
404
+ task_info={"task_name": self.task_name} if self.task_name else None,
405
+ last_action_error=self._last_action_error,
406
+ done=done,
407
+ reward=reward,
408
+ metadata={"cumulative_reward": self._episode_reward},
409
+ )
410
+
411
+ def _execute_click(self, bid: str) -> float:
412
+ """Execute click action. Returns reward.
413
+
414
+ Supports two modes:
415
+ 1. CSS selector mode: If bid starts with '#', '.', or '[', it's treated as a CSS selector
416
+ and uses Playwright directly (e.g., bid="#msg-input")
417
+ 2. BrowserGym mode: Otherwise, uses BrowserGym's accessibility tree bid
418
+ """
419
+ if self._browser_env is None:
420
+ return 0.0
421
+
422
+ try:
423
+ # Check if bid is a CSS selector (starts with # or other CSS selector chars)
424
+ if bid.startswith('#') or bid.startswith('.') or bid.startswith('['):
425
+ # Use Playwright directly for CSS selectors
426
+ return self._execute_click_playwright(bid)
427
+
428
+ # BrowserGym action format: click("bid")
429
+ action = f'click("{bid}")'
430
+ obs, reward, done, truncated, info = self._browser_env.step(action)
431
+
432
+ # Update current state from observation
433
+ self._current_url = obs.get("url", self._current_url)
434
+ self._current_html = obs.get("dom_txt", self._current_html)
435
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
436
+
437
+ return float(reward) if reward else 0.0
438
+ except Exception as e:
439
+ self._last_action_error = f"Click failed: {str(e)}"
440
+ return -0.1
441
+
442
+ def _execute_fill(self, bid: str, text: str) -> float:
443
+ """Execute fill action. Returns reward.
444
+
445
+ Supports two modes:
446
+ 1. CSS selector mode: If bid starts with '#', it's treated as an HTML ID selector
447
+ and uses Playwright directly (e.g., bid="#msg-input")
448
+ 2. BrowserGym mode: Otherwise, uses BrowserGym's accessibility tree bid
449
+ """
450
+ if self._browser_env is None:
451
+ return 0.0
452
+
453
+ try:
454
+ # Check if bid is a CSS selector (starts with # or other CSS selector chars)
455
+ if bid.startswith('#') or bid.startswith('.') or bid.startswith('['):
456
+ # Use Playwright directly for CSS selectors
457
+ return self._execute_fill_playwright(bid, text)
458
+
459
+ # BrowserGym action format: fill("bid", "text")
460
+ action = f'fill("{bid}", "{text}")'
461
+ obs, reward, done, truncated, info = self._browser_env.step(action)
462
+
463
+ # Update current state from observation
464
+ self._current_url = obs.get("url", self._current_url)
465
+ self._current_html = obs.get("dom_txt", self._current_html)
466
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
467
+
468
+ return float(reward) if reward else 0.0
469
+ except Exception as e:
470
+ self._last_action_error = f"Fill failed: {str(e)}"
471
+ return -0.1
472
+
473
+ def _execute_fill_playwright(self, selector: str, text: str) -> float:
474
+ """Execute fill action using Playwright directly with CSS selector."""
475
+ try:
476
+ # Access the underlying Playwright page from BrowserGym
477
+ page = self._browser_env.unwrapped.page
478
+
479
+ # Wait for element and fill it
480
+ page.wait_for_selector(selector, timeout=5000)
481
+ page.fill(selector, text)
482
+
483
+ # Small delay to let the page update
484
+ page.wait_for_timeout(200)
485
+
486
+ # Update observation after action
487
+ self._update_observation_from_page(page)
488
+
489
+ return 0.0
490
+ except Exception as e:
491
+ self._last_action_error = f"Fill (Playwright) failed: {str(e)}"
492
+ return -0.1
493
+
494
+ def _execute_click_playwright(self, selector: str) -> float:
495
+ """Execute click action using Playwright directly with CSS selector."""
496
+ try:
497
+ # Access the underlying Playwright page from BrowserGym
498
+ page = self._browser_env.unwrapped.page
499
+
500
+ # Wait for element and click it
501
+ page.wait_for_selector(selector, timeout=5000)
502
+ page.click(selector)
503
+
504
+ # Longer delay to let HTMX process the request
505
+ page.wait_for_timeout(500)
506
+
507
+ # Update observation after action
508
+ self._update_observation_from_page(page)
509
+
510
+ return 0.0
511
+ except Exception as e:
512
+ self._last_action_error = f"Click (Playwright) failed: {str(e)}"
513
+ return -0.1
514
+
515
+ def _execute_press_key_playwright(self, key: str) -> float:
516
+ """Execute key press using Playwright directly."""
517
+ try:
518
+ # Access the underlying Playwright page from BrowserGym
519
+ page = self._browser_env.unwrapped.page
520
+
521
+ # Press the key
522
+ page.keyboard.press(key)
523
+
524
+ # Delay to let the page update
525
+ page.wait_for_timeout(500)
526
+
527
+ # Update observation after action
528
+ self._update_observation_from_page(page)
529
+
530
+ return 0.0
531
+ except Exception as e:
532
+ self._last_action_error = f"Press key (Playwright) failed: {str(e)}"
533
+ return -0.1
534
+
535
+ def _update_observation_from_page(self, page) -> None:
536
+ """Update internal observation state from Playwright page."""
537
+ try:
538
+ self._current_url = page.url
539
+ # Note: We can't easily get axtree from Playwright directly,
540
+ # so we'll just update URL. The next BrowserGym action will sync the state.
541
+ except Exception:
542
+ pass
543
+
544
+ def _execute_select(self, bid: str, value: str) -> float:
545
+ """Execute select option action. Returns reward."""
546
+ if self._browser_env is None:
547
+ return 0.0
548
+
549
+ try:
550
+ # BrowserGym action format: select_option("bid", "value")
551
+ action = f'select_option("{bid}", "{value}")'
552
+ obs, reward, done, truncated, info = self._browser_env.step(action)
553
+
554
+ # Update current state from observation
555
+ self._current_url = obs.get("url", self._current_url)
556
+ self._current_html = obs.get("dom_txt", self._current_html)
557
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
558
+
559
+ return float(reward) if reward else 0.0
560
+ except Exception as e:
561
+ self._last_action_error = f"Select failed: {str(e)}"
562
+ return -0.1
563
+
564
+ def _execute_goto(self, url: str) -> float:
565
+ """Execute navigation action. Returns reward."""
566
+ if self._browser_env is None:
567
+ self._current_url = url
568
+ return 0.0
569
+
570
+ try:
571
+ # BrowserGym action format: goto("url")
572
+ action = f'goto("{url}")'
573
+ obs, reward, done, truncated, info = self._browser_env.step(action)
574
+
575
+ # Update current state from observation
576
+ self._current_url = obs.get("url", url)
577
+ self._current_html = obs.get("dom_txt", self._current_html)
578
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
579
+
580
+ return float(reward) if reward else 0.0
581
+ except Exception as e:
582
+ self._last_action_error = f"Goto failed: {str(e)}"
583
+ self._current_url = url # Update URL even if failed
584
+ return -0.1
585
+
586
+ def _execute_scroll(self, direction: str) -> float:
587
+ """Execute scroll action. Returns reward."""
588
+ if self._browser_env is None:
589
+ return 0.0
590
+
591
+ try:
592
+ # BrowserGym action format: scroll("direction")
593
+ action = f'scroll("{direction}")'
594
+ obs, reward, done, truncated, info = self._browser_env.step(action)
595
+
596
+ # Update current state from observation
597
+ self._current_url = obs.get("url", self._current_url)
598
+ self._current_html = obs.get("dom_txt", self._current_html)
599
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
600
+
601
+ return float(reward) if reward else 0.0
602
+ except Exception as e:
603
+ self._last_action_error = f"Scroll failed: {str(e)}"
604
+ return -0.1
605
+
606
+ def _execute_send_keys(self, text: str) -> float:
607
+ """Execute send keys action. Returns reward."""
608
+ if self._browser_env is None:
609
+ return 0.0
610
+
611
+ try:
612
+ # Special handling for Enter key - use Playwright directly for reliability
613
+ if text == "\n" or text.lower() == "enter":
614
+ return self._execute_press_key_playwright("Enter")
615
+
616
+ # BrowserGym action format: send_keys("text")
617
+ action = f'send_keys("{text}")'
618
+ obs, reward, done, truncated, info = self._browser_env.step(action)
619
+
620
+ # Update current state from observation
621
+ self._current_url = obs.get("url", self._current_url)
622
+ self._current_html = obs.get("dom_txt", self._current_html)
623
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
624
+
625
+ return float(reward) if reward else 0.0
626
+ except Exception as e:
627
+ self._last_action_error = f"Send keys failed: {str(e)}"
628
+ return -0.1
629
+
630
+ @property
631
+ def state(self) -> State:
632
+ """
633
+ Get the current environment state.
634
+
635
+ Returns:
636
+ Current State with episode_id and step_count
637
+ """
638
+ return self._state
639
+
640
+ def close(self):
641
+ """Clean up resources."""
642
+ if hasattr(self, "_browser_env") and self._browser_env is not None:
643
+ try:
644
+ self._browser_env.close()
645
+ except Exception:
646
+ pass
647
+ self._browser_env = None
648
+
649
+ if hasattr(self, "_apps_process") and self._apps_process is not None:
650
+ try:
651
+ self._apps_process.terminate()
652
+ self._apps_process.wait(timeout=5)
653
+ except Exception:
654
+ self._apps_process.kill()
655
+ self._apps_process = None
656
+
657
+ def __del__(self):
658
+ """Cleanup on deletion."""
659
+ self.close()
envs/openapp_env/server/start.sh ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ # All rights reserved.
4
+ #
5
+ # This source code is licensed under the BSD-style license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ # Startup script for OpenApp Environment Docker container
9
+ # This script starts both the OpenApps server and the FastAPI environment server
10
+
11
+ set -e
12
+
13
+ echo "Starting OpenApp Environment..."
14
+
15
+ # Start OpenApps server in the background
16
+ echo "Starting OpenApps server on port ${OPENAPPS_PORT:-5001}..."
17
+ cd /app/openapps
18
+ # Run launch.py directly - it uses Hydra and needs the config directory
19
+ # Redirect OpenApps output to a log file so we can debug if needed
20
+ python launch.py > /tmp/openapps.log 2>&1 &
21
+ OPENAPPS_PID=$!
22
+
23
+ # Wait for OpenApps server to be ready
24
+ echo "Waiting for OpenApps server to be ready..."
25
+ for i in {1..60}; do
26
+ # Check if OpenApps server is responding using curl
27
+ if curl -sf http://localhost:${OPENAPPS_PORT:-5001} >/dev/null 2>&1; then
28
+ echo "OpenApps server is ready on port ${OPENAPPS_PORT:-5001}!"
29
+ break
30
+ fi
31
+ if [ $i -eq 60 ]; then
32
+ echo "ERROR: OpenApps server failed to start within 60 seconds"
33
+ echo "OpenApps log output:"
34
+ cat /tmp/openapps.log || echo "No log file found"
35
+ kill $OPENAPPS_PID 2>/dev/null || true
36
+ exit 1
37
+ fi
38
+ sleep 1
39
+ done
40
+
41
+ # Start the FastAPI environment server
42
+ echo "Starting FastAPI environment server on port 8000..."
43
+ cd /app/env
44
+ exec uvicorn openapp_env.server.app:app --host 0.0.0.0 --port 8000
envs/openapp_env/test_openapp_env.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ # All rights reserved.
4
+ #
5
+ # This source code is licensed under the BSD-style license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ """
9
+ Simple test script for OpenApp Environment.
10
+
11
+ This script tests the basic functionality of the OpenApp environment
12
+ to ensure it follows OpenEnv standards.
13
+
14
+ Usage:
15
+ # From OpenEnv root directory
16
+ python3 envs/openapp_env/test_openapp_env.py
17
+
18
+ # Or from openapp_env directory
19
+ cd envs/openapp_env
20
+ python3 test_openapp_env.py
21
+ """
22
+
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ # Add src to path for local testing
27
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
28
+
29
+ from openapp_env.models import OpenAppAction, OpenAppObservation
30
+ from openapp_env.server.openapp_environment import OpenAppEnvironment
31
+
32
+
33
+ def test_models():
34
+ """Test that models are properly defined."""
35
+ print("Testing models...")
36
+
37
+ # Test creating an action
38
+ action = OpenAppAction(action_type="noop")
39
+ assert action.action_type == "noop"
40
+
41
+ # Test click action
42
+ click_action = OpenAppAction(action_type="click", bid="test-btn")
43
+ assert click_action.bid == "test-btn"
44
+
45
+ # Test fill action
46
+ fill_action = OpenAppAction(action_type="fill", bid="input", text="Hello")
47
+ assert fill_action.text == "Hello"
48
+
49
+ print("✓ Models test passed")
50
+
51
+
52
+ def test_environment_basic():
53
+ """Test basic environment functionality."""
54
+ print("\nTesting environment...")
55
+
56
+ try:
57
+ # Create environment (note: this will check if OpenApps is installed as a package)
58
+ env = OpenAppEnvironment(
59
+ max_steps=10,
60
+ )
61
+
62
+ # Test that environment has required methods
63
+ assert hasattr(env, "reset")
64
+ assert hasattr(env, "step")
65
+ assert hasattr(env, "state")
66
+ assert hasattr(env, "close")
67
+
68
+ print("✓ Environment structure test passed")
69
+
70
+ except (ValueError, ImportError) as e:
71
+ # Expected if OpenApps is not installed as a package
72
+ if "OpenApps not found" in str(e) or "open_apps" in str(e):
73
+ print(
74
+ "✓ Environment structure test passed (OpenApps not installed, expected)"
75
+ )
76
+ else:
77
+ raise
78
+
79
+
80
+ def test_client_server_contract():
81
+ """Test that client and server follow the contract."""
82
+ print("\nTesting client-server contract...")
83
+
84
+ # Test that action can be serialized to dict
85
+ action = OpenAppAction(
86
+ action_type="click", bid="test-123", metadata={"test": "value"}
87
+ )
88
+
89
+ # Simulate what client._step_payload would do
90
+ payload = {
91
+ "action_type": action.action_type,
92
+ "bid": action.bid,
93
+ "metadata": action.metadata,
94
+ }
95
+
96
+ assert payload["action_type"] == "click"
97
+ assert payload["bid"] == "test-123"
98
+
99
+ # Test observation construction
100
+ obs = OpenAppObservation(
101
+ html="<html></html>",
102
+ url="http://localhost:5001",
103
+ open_pages_urls=["http://localhost:5001"],
104
+ done=False,
105
+ reward=0.0,
106
+ )
107
+
108
+ assert obs.url == "http://localhost:5001"
109
+ assert obs.done is False
110
+
111
+ print("✓ Client-server contract test passed")
112
+
113
+
114
+ def main():
115
+ """Run all tests."""
116
+ print("=" * 60)
117
+ print("OpenApp Environment - Structure Tests")
118
+ print("=" * 60)
119
+
120
+ try:
121
+ test_models()
122
+ test_environment_basic()
123
+ test_client_server_contract()
124
+
125
+ print("\n" + "=" * 60)
126
+ print("All tests passed! ✓")
127
+ print("=" * 60)
128
+ print("\nNote: Full integration tests require:")
129
+ print(
130
+ "1. OpenApps installed: pip install git+https://github.com/facebookresearch/OpenApps.git"
131
+ )
132
+ print("2. Playwright browsers installed: playwright install chromium")
133
+ print("3. BrowserGym dependencies installed")
134
+
135
+ except Exception as e:
136
+ print(f"\n✗ Test failed: {e}")
137
+ import traceback
138
+
139
+ traceback.print_exc()
140
+ sys.exit(1)
141
+
142
+
143
+ if __name__ == "__main__":
144
+ main()
example_usage.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ # All rights reserved.
4
+ #
5
+ # This source code is licensed under the BSD-style license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ """
9
+ Example usage of OpenApp Environment.
10
+
11
+ This script demonstrates how to use the OpenApp environment with OpenEnv.
12
+
13
+ For a complete runnable example, see: examples/openapp_example.py
14
+
15
+ Visualization Options:
16
+ To see the browser window and watch agent interactions:
17
+
18
+ Terminal 1: Start OpenApps server with visible browser
19
+ cd OpenApps
20
+ python OpenApps/launch.py browsergym_env_args.headless=False
21
+
22
+ Terminal 2: Run your agent code
23
+ export OPENAPPS_URL=http://localhost:5001
24
+ python examples/openapp_example.py --mode local
25
+
26
+ Or access OpenApps web interface at http://localhost:5001
27
+ Docker mode web interface at http://localhost:8000/web
28
+
29
+ Important:
30
+ Browser visualization is controlled by the OpenApps SERVER, not the client.
31
+ Launch the server with 'browsergym_env_args.headless=False' to see the browser.
32
+ """
33
+
34
+ import sys
35
+ from pathlib import Path
36
+
37
+ # Add src to path for local testing
38
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
39
+
40
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
41
+
42
+
43
+ def example_basic_usage():
44
+ """Basic usage example."""
45
+ print("=" * 60)
46
+ print("OpenApp Environment - Basic Usage Example")
47
+ print("=" * 60)
48
+
49
+ # Option 1: Connect to a running server
50
+ print("\nOption 1: Connect to running server")
51
+ print("client = OpenAppEnv(base_url='http://localhost:8000')")
52
+
53
+ # Option 2: Start from Docker image (recommended)
54
+ print("\nOption 2: Start from Docker image")
55
+ print("client = OpenAppEnv.from_docker_image('openapp-env:latest')")
56
+
57
+ print("\n" + "-" * 60)
58
+
59
+
60
+ def example_actions():
61
+ """Example of different action types."""
62
+ print("\nExample Actions")
63
+ print("-" * 60)
64
+
65
+ # Navigate to a page
66
+ print("\n1. Navigate to calendar app:")
67
+ print("action = OpenAppAction(")
68
+ print(" action_type='goto',")
69
+ print(" url='http://localhost:5001/calendar'")
70
+ print(")")
71
+ print("result = client.step(action)")
72
+
73
+ # Click on an element
74
+ print("\n2. Click on a button:")
75
+ print("action = OpenAppAction(")
76
+ print(" action_type='click',")
77
+ print(" bid='add-event-btn' # BrowserGym element ID")
78
+ print(")")
79
+ print("result = client.step(action)")
80
+
81
+ # Fill a form field
82
+ print("\n3. Fill in text input:")
83
+ print("action = OpenAppAction(")
84
+ print(" action_type='fill',")
85
+ print(" bid='event-title-input',")
86
+ print(" text='Team Meeting'")
87
+ print(")")
88
+ print("result = client.step(action)")
89
+
90
+ # Select from dropdown
91
+ print("\n4. Select from dropdown:")
92
+ print("action = OpenAppAction(")
93
+ print(" action_type='select_option',")
94
+ print(" bid='time-select',")
95
+ print(" value='14:00'")
96
+ print(")")
97
+ print("result = client.step(action)")
98
+
99
+ # Scroll the page
100
+ print("\n5. Scroll down:")
101
+ print("action = OpenAppAction(")
102
+ print(" action_type='scroll',")
103
+ print(" direction='down'")
104
+ print(")")
105
+ print("result = client.step(action)")
106
+
107
+ # No operation
108
+ print("\n6. No operation (useful for observation):")
109
+ print("action = OpenAppAction(action_type='noop')")
110
+ print("result = client.step(action)")
111
+
112
+
113
+ def example_observations():
114
+ """Example of observation structure."""
115
+ print("\n\nObservation Structure")
116
+ print("-" * 60)
117
+
118
+ print("\nAfter reset() or step(), you receive:")
119
+ print("result.observation.html # Current page HTML")
120
+ print("result.observation.url # Current URL")
121
+ print("result.observation.open_pages_urls # All open pages")
122
+ print("result.observation.axtree_txt # Accessibility tree")
123
+ print("result.observation.app_state # App states (calendar, todo, etc.)")
124
+ print("result.observation.task_info # Task information (if using tasks)")
125
+ print("result.observation.screenshot # Page screenshot (base64)")
126
+ print("result.observation.last_action_error # Error from last action")
127
+ print("result.reward # Step reward")
128
+ print("result.done # Episode done flag")
129
+
130
+
131
+ def example_complete_workflow():
132
+ """Complete workflow example."""
133
+ print("\n\nComplete Workflow Example")
134
+ print("=" * 60)
135
+
136
+ example_code = """
137
+ from envs.openapp_env import OpenAppAction, OpenAppEnv
138
+
139
+ # Create client (starts Docker container)
140
+ client = OpenAppEnv.from_docker_image("openapp-env:latest")
141
+
142
+ try:
143
+ # Reset environment
144
+ result = client.reset()
145
+ print(f"Starting at: {result.observation.url}")
146
+
147
+ # Navigate to calendar
148
+ result = client.step(OpenAppAction(
149
+ action_type="goto",
150
+ url="http://localhost:5001/calendar"
151
+ ))
152
+
153
+ # Click to add new event
154
+ result = client.step(OpenAppAction(
155
+ action_type="click",
156
+ bid="new-event-button"
157
+ ))
158
+
159
+ # Fill event title
160
+ result = client.step(OpenAppAction(
161
+ action_type="fill",
162
+ bid="title-input",
163
+ text="Project Review Meeting"
164
+ ))
165
+
166
+ # Fill event date
167
+ result = client.step(OpenAppAction(
168
+ action_type="fill",
169
+ bid="date-input",
170
+ text="2025-12-15"
171
+ ))
172
+
173
+ # Submit form
174
+ result = client.step(OpenAppAction(
175
+ action_type="click",
176
+ bid="submit-button"
177
+ ))
178
+
179
+ print(f"Reward: {result.reward}")
180
+ print(f"Done: {result.done}")
181
+ print(f"App State: {result.observation.app_state}")
182
+
183
+ finally:
184
+ # Always cleanup
185
+ client.close()
186
+ """
187
+
188
+ print(example_code)
189
+
190
+
191
+ def example_with_tasks():
192
+ """Example using OpenApps tasks."""
193
+ print("\n\nUsing Tasks (Task-Based RL)")
194
+ print("=" * 60)
195
+
196
+ example_code = """
197
+ # Environment can be configured with specific tasks
198
+ # Tasks define goals and automatic reward calculation
199
+
200
+ from envs.openapp_env.server.openapp_environment import OpenAppEnvironment
201
+
202
+ env = OpenAppEnvironment(
203
+ openapps_url="http://localhost:5001", # OpenApps server URL
204
+ task_name="add_meeting_with_dennis", # Optional task name
205
+ headless=False, # Set to False to watch the browser
206
+ max_steps=50,
207
+ )
208
+
209
+ obs = env.reset()
210
+ # Now the environment has a goal: add a meeting with Dennis
211
+ # Rewards will be based on progress toward this goal
212
+
213
+ # Agent loop
214
+ done = False
215
+ while not done:
216
+ action = agent.get_action(obs) # Your agent
217
+ obs = env.step(action)
218
+ done = obs.done
219
+
220
+ print(f"Task completed! Reward: {obs.reward}")
221
+ env.close()
222
+ """
223
+
224
+ print(example_code)
225
+
226
+
227
+ def example_visualization():
228
+ """Example of visualization options."""
229
+ print("\n\nVisualization Options")
230
+ print("=" * 60)
231
+
232
+ example_code = """
233
+ # Option 1: Show browser window (watch agent in real-time)
234
+ from envs.openapp_env.server.openapp_environment import OpenAppEnvironment
235
+
236
+ env = OpenAppEnvironment(
237
+ openapps_url="http://localhost:5001",
238
+ headless=False, # Show browser window
239
+ )
240
+
241
+ obs = env.reset()
242
+ # You'll see a browser window open!
243
+
244
+ # Option 2: Access web interface manually
245
+ # While OpenApps server is running, open in your browser:
246
+ # - Main: http://localhost:5001
247
+ # - Calendar: http://localhost:5001/calendar
248
+ # - Todo: http://localhost:5001/todo
249
+ # - Messenger: http://localhost:5001/messenger
250
+ # - Maps: http://localhost:5001/maps
251
+
252
+ # Option 3: Use the example script with --show-browser
253
+ # python examples/openapp_example.py --mode local --show-browser
254
+ """
255
+
256
+ print(example_code)
257
+
258
+
259
+ def main():
260
+ """Run all examples."""
261
+ example_basic_usage()
262
+ example_actions()
263
+ example_observations()
264
+ example_complete_workflow()
265
+ example_with_tasks()
266
+ example_visualization()
267
+
268
+ print("\n" + "=" * 60)
269
+ print("For a complete runnable example:")
270
+ print(" python examples/openapp_example.py --mode local --show-browser")
271
+ print("\nFor more information, see:")
272
+ print("- README.md in this directory")
273
+ print("- OpenApps docs: https://facebookresearch.github.io/OpenApps/")
274
+ print("- OpenEnv docs: https://meta-pytorch.org/OpenEnv/")
275
+ print("=" * 60)
276
+
277
+
278
+ if __name__ == "__main__":
279
+ main()
models.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ Data models for the OpenApp Environment.
9
+
10
+ The OpenApp environment provides a simulated web application environment
11
+ for training and evaluating UI agents that interact with various apps
12
+ (calendar, todo, messenger, maps, etc.) using browser actions.
13
+ """
14
+
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from pydantic import Field
18
+
19
+ # Support both in-repo and standalone imports
20
+ try:
21
+ # In-repo imports (when running from OpenEnv repository)
22
+ from openenv.core.env_server.types import Action, Observation
23
+ except ImportError:
24
+ # Standalone imports (when environment is standalone with openenv-core from pip)
25
+ from openenv.core.env_server.types import Action, Observation
26
+
27
+
28
+ class OpenAppAction(Action):
29
+ """
30
+ Action for the OpenApp environment.
31
+
32
+ Supports BrowserGym-style actions for web interaction:
33
+ - click: Click on an element (requires bid - BrowserGym ID)
34
+ - fill: Fill a text field (requires bid and text)
35
+ - select_option: Select from dropdown (requires bid and value)
36
+ - goto: Navigate to URL (requires url)
37
+ - scroll: Scroll the page (requires direction)
38
+ - send_keys: Send keyboard input (requires text)
39
+ - noop: No operation
40
+
41
+ Attributes:
42
+ action_type: Type of action to perform
43
+ bid: BrowserGym element ID (for click, fill, select_option)
44
+ text: Text content (for fill, send_keys)
45
+ value: Value to select (for select_option)
46
+ url: URL to navigate to (for goto)
47
+ direction: Scroll direction - 'up' or 'down' (for scroll)
48
+ """
49
+
50
+ action_type: str = Field(
51
+ ..., description="Type of action: click, fill, select_option, goto, scroll, send_keys, noop"
52
+ )
53
+ bid: Optional[str] = Field(default=None, description="BrowserGym element ID")
54
+ text: Optional[str] = Field(default=None, description="Text content for fill or send_keys")
55
+ value: Optional[str] = Field(default=None, description="Value for select_option")
56
+ url: Optional[str] = Field(default=None, description="URL for goto action")
57
+ direction: Optional[str] = Field(default=None, description="Scroll direction: 'up' or 'down'")
58
+
59
+
60
+ class OpenAppObservation(Observation):
61
+ """
62
+ Observation from the OpenApp environment.
63
+
64
+ Provides comprehensive state information about the web apps and browser state.
65
+
66
+ Attributes:
67
+ html: Current page HTML content
68
+ url: Current page URL
69
+ open_pages_urls: List of all open page URLs
70
+ active_page_index: Index of currently active page
71
+ screenshot: Base64-encoded screenshot (optional)
72
+ axtree_txt: Accessibility tree as text (for element interaction)
73
+ app_state: Current state of all apps (calendar, todo, messenger, map)
74
+ task_info: Information about the current task (if any)
75
+ last_action_error: Error message from last action (if failed)
76
+ """
77
+
78
+ html: str = Field(default="", description="Current page HTML content")
79
+ url: str = Field(default="", description="Current page URL")
80
+ open_pages_urls: List[str] = Field(default_factory=list, description="List of all open page URLs")
81
+ active_page_index: int = Field(default=0, ge=0, description="Index of currently active page")
82
+ screenshot: Optional[str] = Field(default=None, description="Base64-encoded screenshot")
83
+ axtree_txt: str = Field(default="", description="Accessibility tree as text")
84
+ app_state: Dict[str, Any] = Field(default_factory=dict, description="State of all apps")
85
+ task_info: Optional[Dict[str, Any]] = Field(default=None, description="Current task information")
86
+ last_action_error: Optional[str] = Field(default=None, description="Error from last action")
openenv.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: openapp_env
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
pyproject.toml ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ [build-system]
8
+ requires = ["setuptools>=45", "wheel"]
9
+ build-backend = "setuptools.build_meta"
10
+
11
+ [project]
12
+ name = "openenv-openapp_env"
13
+ version = "0.1.0"
14
+ description = "OpenApp Environment for OpenEnv - web application simulation environment for UI agents"
15
+ requires-python = ">=3.11,<3.14"
16
+ dependencies = [
17
+ # NOTE: openenv-core is NOT listed here to avoid openai version conflict
18
+ # It is installed separately in the Dockerfile with --no-deps to avoid
19
+ # openai>=2.7.2 conflicting with OpenApps' openai<2 requirement.
20
+ # For local development, install manually:
21
+ # pip install --no-deps "openenv-core @ git+https://github.com/meta-pytorch/OpenEnv.git"
22
+ # pip install fastapi pydantic uvicorn requests websockets
23
+ #
24
+ # NOTE: open_apps is also NOT listed here for the same reason.
25
+ # Install manually for local development:
26
+ # pip install git+https://github.com/facebookresearch/OpenApps.git
27
+ #
28
+ # Server dependencies (these are installed by Dockerfile separately for openenv-core)
29
+ "fastapi>=0.115.0",
30
+ "pydantic>=2.0.0",
31
+ "uvicorn[standard]>=0.24.0",
32
+ "requests>=2.31.0",
33
+ "websockets>=15.0.1",
34
+ # BrowserGym dependencies
35
+ "browsergym>=0.13.3",
36
+ "playwright>=1.40.0",
37
+ # Additional dependencies for web app interaction
38
+ "python-multipart>=0.0.20",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "pytest>=8.0.0",
44
+ "pytest-cov>=4.0.0",
45
+ ]
46
+
47
+ [project.scripts]
48
+ server = "openapp_env.server.app:main"
49
+
50
+ [tool.setuptools]
51
+ packages = ["openapp_env", "openapp_env.server"]
52
+ package-dir = { "openapp_env" = ".", "openapp_env.server" = "server" }
53
+
54
+ [tool.setuptools.package-data]
55
+ openapp_env = ["**/*.yaml", "**/*.yml", "**/*.md"]
56
+
57
+ [tool.hatch.metadata]
58
+ allow-direct-references = true
server/Dockerfile ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ # Dockerfile for OpenApp Environment
8
+ # This image provides OpenApps web application simulation for UI agent training
9
+ #
10
+ # This Dockerfile works for both local builds and HuggingFace Spaces deployment:
11
+ # - Local build: cd envs/openapp_env && docker build -t openapp-env:latest -f server/Dockerfile .
12
+ # - HuggingFace: Automatically deployed via `openenv push`
13
+ #
14
+ # Run with web interface:
15
+ # docker run -p 8000:8000 -e ENABLE_WEB_INTERFACE=true openapp-env:latest
16
+
17
+ FROM python:3.11-slim
18
+
19
+ # Set metadata
20
+ LABEL maintainer="OpenEnv Team"
21
+ LABEL description="OpenApp Environment with BrowserGym for UI agent training"
22
+ LABEL org.opencontainers.image.source="https://github.com/meta-pytorch/OpenEnv"
23
+
24
+ # Set working directory
25
+ WORKDIR /app/env
26
+
27
+ # Install system dependencies
28
+ # - git: required to clone OpenApps from GitHub
29
+ # - curl: for healthcheck
30
+ # - Playwright/BrowserGym dependencies: fonts, libraries for browser automation
31
+ RUN apt-get update && \
32
+ apt-get install -y --no-install-recommends \
33
+ git \
34
+ curl \
35
+ ca-certificates \
36
+ wget \
37
+ gnupg \
38
+ # Playwright/Chromium dependencies
39
+ libnss3 \
40
+ libnspr4 \
41
+ libatk1.0-0 \
42
+ libatk-bridge2.0-0 \
43
+ libcups2 \
44
+ libdrm2 \
45
+ libdbus-1-3 \
46
+ libxkbcommon0 \
47
+ libxcomposite1 \
48
+ libxdamage1 \
49
+ libxfixes3 \
50
+ libxrandr2 \
51
+ libgbm1 \
52
+ libasound2 \
53
+ libpango-1.0-0 \
54
+ libcairo2 \
55
+ libatspi2.0-0 \
56
+ libxshmfence1 \
57
+ fonts-liberation \
58
+ libappindicator3-1 \
59
+ xdg-utils && \
60
+ rm -rf /var/lib/apt/lists/*
61
+
62
+ # Set environment variables
63
+ ENV PYTHONUNBUFFERED=1
64
+
65
+ # Set working directory
66
+ WORKDIR /app/env
67
+
68
+ # Copy environment files
69
+ # Context is always the env directory (envs/openapp_env/)
70
+ # - GitHub Actions: uses context: envs/openapp_env
71
+ # - HuggingFace: openenv push uploads env dir as context
72
+ COPY . /app/env
73
+
74
+ # Install OpenApps FIRST to establish openai<2 (required by agentlab)
75
+ # This must happen before openenv-core to avoid version conflict
76
+ WORKDIR /app
77
+ RUN git clone https://github.com/facebookresearch/OpenApps.git openapps && \
78
+ cd openapps && \
79
+ pip install --no-cache-dir -e .
80
+
81
+ # Verify OpenApps installation
82
+ RUN python -c "import open_apps; print('✓ OpenApps installed')"
83
+
84
+ # Install openenv-core from GitHub with --no-deps to avoid openai>=2.7.2 conflict
85
+ # Then install only the server dependencies (no openai needed for server)
86
+ RUN pip install --no-cache-dir --no-deps "openenv-core[core]>=0.2.1" && \
87
+ pip install --no-cache-dir fastapi pydantic uvicorn requests websockets
88
+
89
+ # Install openapp_env and remaining dependencies
90
+ WORKDIR /app/env
91
+ RUN pip install --no-cache-dir -e .
92
+
93
+ # Verify installation
94
+ RUN python -c "import openapp_env; print('✓ openapp_env installed')" && \
95
+ python -c "import openapp_env.server.app; print('✓ openapp_env.server.app importable')"
96
+
97
+ # Install Playwright browsers (Chromium for BrowserGym)
98
+ # We already installed system dependencies above, so just install the browser
99
+ RUN playwright install chromium
100
+
101
+ # Copy startup script
102
+ WORKDIR /app/env
103
+ COPY server/start.sh /app/start.sh
104
+ RUN chmod +x /app/start.sh
105
+
106
+ # OpenApp-specific environment variables (can be overridden at runtime)
107
+ ENV OPENAPPS_URL=http://localhost:5001
108
+ ENV OPENAPPS_PORT=5001
109
+ ENV OPENAPP_HEADLESS=true
110
+ ENV OPENAPP_MAX_STEPS=50
111
+
112
+ # Hydra requires USER environment variable
113
+ ENV USER=root
114
+
115
+ # Enable web interface by default (set to false to disable)
116
+ ENV ENABLE_WEB_INTERFACE=true
117
+
118
+ # Expose ports (8000 for FastAPI, 5001 for OpenApps)
119
+ EXPOSE 8000 5001
120
+
121
+ # Health check
122
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
123
+ CMD curl -f http://localhost:8000/health || exit 1
124
+
125
+ # Run the startup script that launches both OpenApps server and FastAPI server
126
+ # Web interface will be available at /web if ENABLE_WEB_INTERFACE=true
127
+ # API documentation available at /docs
128
+ CMD ["/app/start.sh"]
server/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """OpenApp Environment Server."""
server/app.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ FastAPI application for the OpenApp Environment.
9
+
10
+ This module creates an HTTP server that exposes the OpenAppEnvironment
11
+ over HTTP endpoints, making it compatible with HTTPEnvClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from ..models import OpenAppAction, OpenAppObservation
29
+ from .openapp_environment import OpenAppEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv-core from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from openapp_env.models import OpenAppAction, OpenAppObservation
34
+ from openapp_env.server.openapp_environment import OpenAppEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ # Each client gets its own environment instance. The environment reads
39
+ # OPENAPPS_URL from environment variables in __init__.
40
+ app = create_app(OpenAppEnvironment, OpenAppAction, OpenAppObservation, env_name="openapp_env")
41
+
42
+
43
+ def main():
44
+ """
45
+ Entry point for direct execution via uv run or python -m.
46
+
47
+ This function enables running the server without Docker:
48
+ uv run --project . server
49
+ python -m envs.openapp_env.server.app
50
+ openenv serve openapp_env
51
+
52
+ """
53
+ import uvicorn
54
+
55
+ uvicorn.run(app, host="0.0.0.0", port=8000)
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
server/openapp_environment.py ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """
8
+ OpenApp Environment Implementation.
9
+
10
+ A web application simulation environment that wraps OpenApps and BrowserGym.
11
+ This environment provides agent interaction with simulated web apps including
12
+ calendar, todo, messenger, and maps applications.
13
+ """
14
+
15
+ import logging
16
+ import os
17
+ import subprocess
18
+ import time
19
+ import urllib.request
20
+ from pathlib import Path
21
+ from typing import Any, Dict, Optional, Tuple
22
+ from uuid import uuid4
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Support both in-repo and standalone imports
27
+ try:
28
+ # In-repo imports (when running from OpenEnv repository)
29
+ from openenv.core.env_server.interfaces import Environment
30
+ from openenv.core.env_server.types import State
31
+ from ..models import OpenAppAction, OpenAppObservation
32
+ except ImportError:
33
+ # Standalone imports (when environment is standalone with openenv-core from pip)
34
+ from openenv.core.env_server.interfaces import Environment
35
+ from openenv.core.env_server.types import State
36
+ from openapp_env.models import OpenAppAction, OpenAppObservation
37
+
38
+
39
+ class GenericOpenAppsTask:
40
+ """
41
+ A generic task for OpenApps interaction without specific goals.
42
+
43
+ This is a simple wrapper that allows BrowserGym to interact with OpenApps
44
+ without requiring a specific task. For task-based interaction, use the
45
+ OpenAppsTask from open_apps.tasks.add_tasks_to_browsergym.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ base_url: str,
51
+ seed: int = 1,
52
+ **kwargs,
53
+ ) -> None:
54
+ """
55
+ Initialize generic OpenApps task.
56
+
57
+ Args:
58
+ base_url: Base URL of the OpenApps server
59
+ seed: Random seed (required by BrowserGym)
60
+ **kwargs: Additional arguments (ignored)
61
+ """
62
+ try:
63
+ from browsergym.core.task import AbstractBrowserTask
64
+ import playwright.sync_api
65
+ except ImportError:
66
+ raise ImportError(
67
+ "BrowserGym is required. Install with: pip install browsergym"
68
+ )
69
+
70
+ # Store as instance attributes
71
+ self.base_url = base_url
72
+ self.seed = seed
73
+
74
+ # BrowserGym task properties
75
+ self.viewport = {"width": 1024, "height": 768}
76
+ self.slow_mo = 100
77
+ self.timeout = 5000
78
+
79
+ # Additional properties that BrowserGym might expect
80
+ self.locale = None
81
+ self.timezone_id = None
82
+ self.geolocation = None
83
+
84
+ def setup(
85
+ self, page: "playwright.sync_api.Page"
86
+ ) -> Tuple[str, Dict[str, Any]]:
87
+ """
88
+ Set up the task by navigating to the base URL.
89
+
90
+ Args:
91
+ page: Playwright page object
92
+
93
+ Returns:
94
+ Tuple of (goal_string, info_dict)
95
+ """
96
+ page.goto(self.base_url)
97
+ return "Explore OpenApps", {}
98
+
99
+ def teardown(self) -> None:
100
+ """Clean up after task completion."""
101
+ pass
102
+
103
+ def validate(
104
+ self, page: "playwright.sync_api.Page", chat_messages: list[str]
105
+ ) -> Tuple[float, bool, str, Dict[str, Any]]:
106
+ """
107
+ Validate task state and return reward.
108
+
109
+ Args:
110
+ page: Playwright page object
111
+ chat_messages: List of chat messages
112
+
113
+ Returns:
114
+ Tuple of (reward, done, message, info)
115
+ """
116
+ # Generic task never completes automatically
117
+ return 0.0, False, "", {}
118
+
119
+ def cheat(
120
+ self, page: "playwright.sync_api.Page", chat_messages: list[str]
121
+ ) -> None:
122
+ """Cheat method (no-op for generic task)."""
123
+ pass
124
+
125
+
126
+ class OpenAppEnvironment(Environment):
127
+ """
128
+ A web application environment that wraps OpenApps and BrowserGym.
129
+
130
+ This environment launches OpenApps web server and provides a BrowserGym-like
131
+ interface for agents to interact with simulated web applications.
132
+
133
+ Args:
134
+ openapps_path: Path to OpenApps directory (default: auto-detect)
135
+ web_app_port: Port for OpenApps web server (default: 5001)
136
+ headless: Run browser in headless mode (default: True)
137
+ task_name: Optional task name to evaluate (e.g., "add_meeting_with_dennis")
138
+ apps_config: Configuration for apps (default: all enabled)
139
+ max_steps: Maximum steps per episode (default: 50)
140
+
141
+ Example:
142
+ >>> env = OpenAppEnvironment()
143
+ >>> obs = env.reset()
144
+ >>> print(obs.url) # Starting page URL
145
+ >>>
146
+ >>> # Click on an element
147
+ >>> action = OpenAppAction(action_type="click", bid="calendar-btn")
148
+ >>> obs = env.step(action)
149
+ >>> print(obs.html)
150
+ """
151
+
152
+ def __init__(
153
+ self,
154
+ openapps_url: Optional[str] = None,
155
+ openapps_path: Optional[str] = None,
156
+ web_app_port: int = 5001,
157
+ headless: bool = True,
158
+ task_name: Optional[str] = None,
159
+ apps_config: Optional[Dict[str, Any]] = None,
160
+ max_steps: int = 50,
161
+ ):
162
+ """Initialize the OpenApp environment."""
163
+ self._state = State(episode_id=str(uuid4()), step_count=0)
164
+ self._max_steps = max_steps
165
+
166
+ # OpenApps configuration
167
+ # Priority: 1. openapps_url, 2. OPENAPPS_URL env var, 3. Try to find/launch
168
+ self.openapps_url = openapps_url or os.environ.get("OPENAPPS_URL")
169
+ if not self.openapps_url:
170
+ self.web_app_port = web_app_port
171
+ self.openapps_url = f"http://localhost:{web_app_port}"
172
+
173
+ self.openapps_path = openapps_path
174
+ self.headless = headless
175
+ self.task_name = task_name
176
+ self.apps_config = apps_config or {}
177
+
178
+ # Runtime state
179
+ self._apps_process: Optional[subprocess.Popen] = None
180
+ self._browser_env = None
181
+ self._current_html = ""
182
+ self._current_url = ""
183
+ self._current_axtree = ""
184
+ self._app_state = {}
185
+ self._last_action_error = None
186
+ self._episode_reward = 0.0
187
+
188
+ def _detect_openapps_path(self) -> str:
189
+ """
190
+ Auto-detect OpenApps path.
191
+
192
+ Since OpenApps is installed as a Python package, we use the installed
193
+ package location instead of requiring a separate directory.
194
+ """
195
+ # Check if user provided a custom path via environment variable
196
+ env_path = os.environ.get("OPENAPPS_PATH")
197
+ if env_path and Path(env_path).exists():
198
+ return env_path
199
+
200
+ # Try to find OpenApps as an installed package
201
+ try:
202
+ import open_apps
203
+
204
+ openapps_pkg_path = Path(open_apps.__file__).parent.parent
205
+ if openapps_pkg_path.exists():
206
+ return str(openapps_pkg_path)
207
+ except ImportError:
208
+ pass
209
+
210
+ raise ValueError(
211
+ "OpenApps not found. Please install it with: "
212
+ "pip install git+https://github.com/facebookresearch/OpenApps.git "
213
+ "or set OPENAPPS_PATH environment variable."
214
+ )
215
+
216
+ def _launch_openapps_server(self) -> Optional[subprocess.Popen]:
217
+ """
218
+ Launch OpenApps web server in background.
219
+
220
+ Returns None if server is expected to be already running (OPENAPPS_URL set).
221
+ """
222
+ # If OPENAPPS_URL is set, assume server is already running
223
+ if os.environ.get("OPENAPPS_URL"):
224
+ logger.info(f"Using existing OpenApps server at {self.openapps_url}")
225
+ # Wait for server to be available
226
+ self._wait_for_server(max_wait=5)
227
+ return None
228
+
229
+ # Otherwise, provide helpful error message
230
+ raise NotImplementedError(
231
+ "Automatic OpenApps server launch is not yet implemented.\n"
232
+ "\n"
233
+ "Please start OpenApps manually in a separate terminal:\n"
234
+ " 1. Clone OpenApps: git clone https://github.com/facebookresearch/OpenApps.git\n"
235
+ " 2. Install: cd OpenApps && uv sync\n"
236
+ " 3. Run: uv run launch.py\n"
237
+ "\n"
238
+ "Then set the OPENAPPS_URL environment variable:\n"
239
+ " export OPENAPPS_URL=http://localhost:5001\n"
240
+ "\n"
241
+ "Or use Docker mode which handles this automatically:\n"
242
+ " python examples/openapp_example.py --mode docker\n"
243
+ )
244
+
245
+ def _wait_for_server(self, max_wait: int = 30):
246
+ """Wait for OpenApps server to become available."""
247
+ for i in range(max_wait):
248
+ try:
249
+ response = urllib.request.urlopen(self.openapps_url, timeout=2)
250
+ if response.status == 200:
251
+ return
252
+ except Exception:
253
+ pass
254
+ time.sleep(1)
255
+
256
+ raise TimeoutError(f"OpenApps server did not start within {max_wait} seconds")
257
+
258
+ def _initialize_browser(self):
259
+ """Initialize BrowserGym environment for interaction."""
260
+ try:
261
+ from browsergym.core.env import BrowserEnv
262
+ except ImportError:
263
+ raise ImportError(
264
+ "BrowserGym is required for OpenApp environment. "
265
+ "Install it with: pip install browsergym"
266
+ )
267
+
268
+ # Create BrowserGym environment with generic OpenApps task
269
+ self._browser_env = BrowserEnv(
270
+ task_entrypoint=GenericOpenAppsTask,
271
+ task_kwargs={"base_url": self.openapps_url},
272
+ headless=self.headless,
273
+ slow_mo=200, # Slow down actions so they're visible (200ms delay)
274
+ )
275
+
276
+ def _get_current_observation(self) -> Dict[str, Any]:
277
+ """Extract current observation from browser state."""
278
+ if self._browser_env is None:
279
+ return {
280
+ "html": "",
281
+ "url": self.openapps_url,
282
+ "open_pages_urls": [self.openapps_url],
283
+ "active_page_index": 0,
284
+ "axtree_txt": "",
285
+ "app_state": {},
286
+ }
287
+
288
+ # Get browser state (implementation depends on BrowserGym API)
289
+ # This is a simplified version - actual implementation would use BrowserGym's observation
290
+ return {
291
+ "html": self._current_html,
292
+ "url": self._current_url,
293
+ "open_pages_urls": [self._current_url],
294
+ "active_page_index": 0,
295
+ "axtree_txt": self._current_axtree,
296
+ "app_state": self._app_state,
297
+ }
298
+
299
+ def reset(self) -> OpenAppObservation:
300
+ """
301
+ Reset the environment.
302
+
303
+ Returns:
304
+ OpenAppObservation with initial state
305
+ """
306
+ # Reset state
307
+ self._state = State(episode_id=str(uuid4()), step_count=0)
308
+ self._episode_reward = 0.0
309
+ self._last_action_error = None
310
+
311
+ # Check if OpenApps server is running, start if needed
312
+ if self._apps_process is None and not os.environ.get("OPENAPPS_URL"):
313
+ self._apps_process = self._launch_openapps_server()
314
+
315
+ # Initialize browser
316
+ if self._browser_env is None:
317
+ self._initialize_browser()
318
+
319
+ # Reset the BrowserGym environment
320
+ try:
321
+ obs, info = self._browser_env.reset()
322
+ # Extract observation data from BrowserGym
323
+ self._current_url = obs.get("url", self.openapps_url)
324
+ self._current_html = obs.get("dom_txt", "")
325
+ self._current_axtree = obs.get("axtree_txt", "")
326
+ self._app_state = {}
327
+ except Exception as e:
328
+ logger.warning(f"Failed to reset browser environment: {e}")
329
+ # Fallback to placeholder values
330
+ self._current_url = self.openapps_url
331
+ self._current_html = "<html><body>OpenApps Ready</body></html>"
332
+ self._current_axtree = ""
333
+ self._app_state = {}
334
+
335
+ obs_data = self._get_current_observation()
336
+
337
+ return OpenAppObservation(
338
+ html=obs_data["html"],
339
+ url=obs_data["url"],
340
+ open_pages_urls=obs_data["open_pages_urls"],
341
+ active_page_index=obs_data["active_page_index"],
342
+ axtree_txt=obs_data["axtree_txt"],
343
+ app_state=obs_data["app_state"],
344
+ task_info={"task_name": self.task_name} if self.task_name else None,
345
+ last_action_error=None,
346
+ done=False,
347
+ reward=0.0,
348
+ )
349
+
350
+ def step(self, action: OpenAppAction) -> OpenAppObservation: # type: ignore[override]
351
+ """
352
+ Execute a step in the environment.
353
+
354
+ Args:
355
+ action: OpenAppAction to execute
356
+
357
+ Returns:
358
+ OpenAppObservation with resulting state and reward
359
+ """
360
+ self._state.step_count += 1
361
+ self._last_action_error = None
362
+ reward = 0.0
363
+
364
+ try:
365
+ # Execute action based on type
366
+ if action.action_type == "click":
367
+ reward = self._execute_click(action.bid)
368
+ elif action.action_type == "fill":
369
+ reward = self._execute_fill(action.bid, action.text)
370
+ elif action.action_type == "select_option":
371
+ reward = self._execute_select(action.bid, action.value)
372
+ elif action.action_type == "goto":
373
+ reward = self._execute_goto(action.url)
374
+ elif action.action_type == "scroll":
375
+ reward = self._execute_scroll(action.direction)
376
+ elif action.action_type == "send_keys":
377
+ reward = self._execute_send_keys(action.text)
378
+ elif action.action_type == "noop":
379
+ reward = 0.0
380
+ else:
381
+ self._last_action_error = f"Unknown action type: {action.action_type}"
382
+ reward = -0.1
383
+
384
+ except Exception as e:
385
+ self._last_action_error = str(e)
386
+ reward = -0.1
387
+
388
+ # Update cumulative reward
389
+ self._episode_reward += reward
390
+
391
+ # Check if episode is done
392
+ done = self._state.step_count >= self._max_steps
393
+
394
+ # Get current observation
395
+ obs_data = self._get_current_observation()
396
+
397
+ return OpenAppObservation(
398
+ html=obs_data["html"],
399
+ url=obs_data["url"],
400
+ open_pages_urls=obs_data["open_pages_urls"],
401
+ active_page_index=obs_data["active_page_index"],
402
+ axtree_txt=obs_data["axtree_txt"],
403
+ app_state=obs_data["app_state"],
404
+ task_info={"task_name": self.task_name} if self.task_name else None,
405
+ last_action_error=self._last_action_error,
406
+ done=done,
407
+ reward=reward,
408
+ metadata={"cumulative_reward": self._episode_reward},
409
+ )
410
+
411
+ def _execute_click(self, bid: str) -> float:
412
+ """Execute click action. Returns reward.
413
+
414
+ Supports two modes:
415
+ 1. CSS selector mode: If bid starts with '#', '.', or '[', it's treated as a CSS selector
416
+ and uses Playwright directly (e.g., bid="#msg-input")
417
+ 2. BrowserGym mode: Otherwise, uses BrowserGym's accessibility tree bid
418
+ """
419
+ if self._browser_env is None:
420
+ return 0.0
421
+
422
+ try:
423
+ # Check if bid is a CSS selector (starts with # or other CSS selector chars)
424
+ if bid.startswith('#') or bid.startswith('.') or bid.startswith('['):
425
+ # Use Playwright directly for CSS selectors
426
+ return self._execute_click_playwright(bid)
427
+
428
+ # BrowserGym action format: click("bid")
429
+ action = f'click("{bid}")'
430
+ obs, reward, done, truncated, info = self._browser_env.step(action)
431
+
432
+ # Update current state from observation
433
+ self._current_url = obs.get("url", self._current_url)
434
+ self._current_html = obs.get("dom_txt", self._current_html)
435
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
436
+
437
+ return float(reward) if reward else 0.0
438
+ except Exception as e:
439
+ self._last_action_error = f"Click failed: {str(e)}"
440
+ return -0.1
441
+
442
+ def _execute_fill(self, bid: str, text: str) -> float:
443
+ """Execute fill action. Returns reward.
444
+
445
+ Supports two modes:
446
+ 1. CSS selector mode: If bid starts with '#', it's treated as an HTML ID selector
447
+ and uses Playwright directly (e.g., bid="#msg-input")
448
+ 2. BrowserGym mode: Otherwise, uses BrowserGym's accessibility tree bid
449
+ """
450
+ if self._browser_env is None:
451
+ return 0.0
452
+
453
+ try:
454
+ # Check if bid is a CSS selector (starts with # or other CSS selector chars)
455
+ if bid.startswith('#') or bid.startswith('.') or bid.startswith('['):
456
+ # Use Playwright directly for CSS selectors
457
+ return self._execute_fill_playwright(bid, text)
458
+
459
+ # BrowserGym action format: fill("bid", "text")
460
+ action = f'fill("{bid}", "{text}")'
461
+ obs, reward, done, truncated, info = self._browser_env.step(action)
462
+
463
+ # Update current state from observation
464
+ self._current_url = obs.get("url", self._current_url)
465
+ self._current_html = obs.get("dom_txt", self._current_html)
466
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
467
+
468
+ return float(reward) if reward else 0.0
469
+ except Exception as e:
470
+ self._last_action_error = f"Fill failed: {str(e)}"
471
+ return -0.1
472
+
473
+ def _execute_fill_playwright(self, selector: str, text: str) -> float:
474
+ """Execute fill action using Playwright directly with CSS selector."""
475
+ try:
476
+ # Access the underlying Playwright page from BrowserGym
477
+ page = self._browser_env.unwrapped.page
478
+
479
+ # Wait for element and fill it
480
+ page.wait_for_selector(selector, timeout=5000)
481
+ page.fill(selector, text)
482
+
483
+ # Small delay to let the page update
484
+ page.wait_for_timeout(200)
485
+
486
+ # Update observation after action
487
+ self._update_observation_from_page(page)
488
+
489
+ return 0.0
490
+ except Exception as e:
491
+ self._last_action_error = f"Fill (Playwright) failed: {str(e)}"
492
+ return -0.1
493
+
494
+ def _execute_click_playwright(self, selector: str) -> float:
495
+ """Execute click action using Playwright directly with CSS selector."""
496
+ try:
497
+ # Access the underlying Playwright page from BrowserGym
498
+ page = self._browser_env.unwrapped.page
499
+
500
+ # Wait for element and click it
501
+ page.wait_for_selector(selector, timeout=5000)
502
+ page.click(selector)
503
+
504
+ # Longer delay to let HTMX process the request
505
+ page.wait_for_timeout(500)
506
+
507
+ # Update observation after action
508
+ self._update_observation_from_page(page)
509
+
510
+ return 0.0
511
+ except Exception as e:
512
+ self._last_action_error = f"Click (Playwright) failed: {str(e)}"
513
+ return -0.1
514
+
515
+ def _execute_press_key_playwright(self, key: str) -> float:
516
+ """Execute key press using Playwright directly."""
517
+ try:
518
+ # Access the underlying Playwright page from BrowserGym
519
+ page = self._browser_env.unwrapped.page
520
+
521
+ # Press the key
522
+ page.keyboard.press(key)
523
+
524
+ # Delay to let the page update
525
+ page.wait_for_timeout(500)
526
+
527
+ # Update observation after action
528
+ self._update_observation_from_page(page)
529
+
530
+ return 0.0
531
+ except Exception as e:
532
+ self._last_action_error = f"Press key (Playwright) failed: {str(e)}"
533
+ return -0.1
534
+
535
+ def _update_observation_from_page(self, page) -> None:
536
+ """Update internal observation state from Playwright page."""
537
+ try:
538
+ self._current_url = page.url
539
+ # Note: We can't easily get axtree from Playwright directly,
540
+ # so we'll just update URL. The next BrowserGym action will sync the state.
541
+ except Exception:
542
+ pass
543
+
544
+ def _execute_select(self, bid: str, value: str) -> float:
545
+ """Execute select option action. Returns reward."""
546
+ if self._browser_env is None:
547
+ return 0.0
548
+
549
+ try:
550
+ # BrowserGym action format: select_option("bid", "value")
551
+ action = f'select_option("{bid}", "{value}")'
552
+ obs, reward, done, truncated, info = self._browser_env.step(action)
553
+
554
+ # Update current state from observation
555
+ self._current_url = obs.get("url", self._current_url)
556
+ self._current_html = obs.get("dom_txt", self._current_html)
557
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
558
+
559
+ return float(reward) if reward else 0.0
560
+ except Exception as e:
561
+ self._last_action_error = f"Select failed: {str(e)}"
562
+ return -0.1
563
+
564
+ def _execute_goto(self, url: str) -> float:
565
+ """Execute navigation action. Returns reward."""
566
+ if self._browser_env is None:
567
+ self._current_url = url
568
+ return 0.0
569
+
570
+ try:
571
+ # BrowserGym action format: goto("url")
572
+ action = f'goto("{url}")'
573
+ obs, reward, done, truncated, info = self._browser_env.step(action)
574
+
575
+ # Update current state from observation
576
+ self._current_url = obs.get("url", url)
577
+ self._current_html = obs.get("dom_txt", self._current_html)
578
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
579
+
580
+ return float(reward) if reward else 0.0
581
+ except Exception as e:
582
+ self._last_action_error = f"Goto failed: {str(e)}"
583
+ self._current_url = url # Update URL even if failed
584
+ return -0.1
585
+
586
+ def _execute_scroll(self, direction: str) -> float:
587
+ """Execute scroll action. Returns reward."""
588
+ if self._browser_env is None:
589
+ return 0.0
590
+
591
+ try:
592
+ # BrowserGym action format: scroll("direction")
593
+ action = f'scroll("{direction}")'
594
+ obs, reward, done, truncated, info = self._browser_env.step(action)
595
+
596
+ # Update current state from observation
597
+ self._current_url = obs.get("url", self._current_url)
598
+ self._current_html = obs.get("dom_txt", self._current_html)
599
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
600
+
601
+ return float(reward) if reward else 0.0
602
+ except Exception as e:
603
+ self._last_action_error = f"Scroll failed: {str(e)}"
604
+ return -0.1
605
+
606
+ def _execute_send_keys(self, text: str) -> float:
607
+ """Execute send keys action. Returns reward."""
608
+ if self._browser_env is None:
609
+ return 0.0
610
+
611
+ try:
612
+ # Special handling for Enter key - use Playwright directly for reliability
613
+ if text == "\n" or text.lower() == "enter":
614
+ return self._execute_press_key_playwright("Enter")
615
+
616
+ # BrowserGym action format: send_keys("text")
617
+ action = f'send_keys("{text}")'
618
+ obs, reward, done, truncated, info = self._browser_env.step(action)
619
+
620
+ # Update current state from observation
621
+ self._current_url = obs.get("url", self._current_url)
622
+ self._current_html = obs.get("dom_txt", self._current_html)
623
+ self._current_axtree = obs.get("axtree_txt", self._current_axtree)
624
+
625
+ return float(reward) if reward else 0.0
626
+ except Exception as e:
627
+ self._last_action_error = f"Send keys failed: {str(e)}"
628
+ return -0.1
629
+
630
+ @property
631
+ def state(self) -> State:
632
+ """
633
+ Get the current environment state.
634
+
635
+ Returns:
636
+ Current State with episode_id and step_count
637
+ """
638
+ return self._state
639
+
640
+ def close(self):
641
+ """Clean up resources."""
642
+ if hasattr(self, "_browser_env") and self._browser_env is not None:
643
+ try:
644
+ self._browser_env.close()
645
+ except Exception:
646
+ pass
647
+ self._browser_env = None
648
+
649
+ if hasattr(self, "_apps_process") and self._apps_process is not None:
650
+ try:
651
+ self._apps_process.terminate()
652
+ self._apps_process.wait(timeout=5)
653
+ except Exception:
654
+ self._apps_process.kill()
655
+ self._apps_process = None
656
+
657
+ def __del__(self):
658
+ """Cleanup on deletion."""
659
+ self.close()
server/start.sh ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ # All rights reserved.
4
+ #
5
+ # This source code is licensed under the BSD-style license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ # Startup script for OpenApp Environment Docker container
9
+ # This script starts both the OpenApps server and the FastAPI environment server
10
+
11
+ set -e
12
+
13
+ echo "Starting OpenApp Environment..."
14
+
15
+ # Start OpenApps server in the background
16
+ echo "Starting OpenApps server on port ${OPENAPPS_PORT:-5001}..."
17
+ cd /app/openapps
18
+ # Run launch.py directly - it uses Hydra and needs the config directory
19
+ # Redirect OpenApps output to a log file so we can debug if needed
20
+ python launch.py > /tmp/openapps.log 2>&1 &
21
+ OPENAPPS_PID=$!
22
+
23
+ # Wait for OpenApps server to be ready
24
+ echo "Waiting for OpenApps server to be ready..."
25
+ for i in {1..60}; do
26
+ # Check if OpenApps server is responding using curl
27
+ if curl -sf http://localhost:${OPENAPPS_PORT:-5001} >/dev/null 2>&1; then
28
+ echo "OpenApps server is ready on port ${OPENAPPS_PORT:-5001}!"
29
+ break
30
+ fi
31
+ if [ $i -eq 60 ]; then
32
+ echo "ERROR: OpenApps server failed to start within 60 seconds"
33
+ echo "OpenApps log output:"
34
+ cat /tmp/openapps.log || echo "No log file found"
35
+ kill $OPENAPPS_PID 2>/dev/null || true
36
+ exit 1
37
+ fi
38
+ sleep 1
39
+ done
40
+
41
+ # Start the FastAPI environment server
42
+ echo "Starting FastAPI environment server on port 8000..."
43
+ cd /app/env
44
+ exec uvicorn openapp_env.server.app:app --host 0.0.0.0 --port 8000
src/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
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
+ """EnvTorch: Standardized agentic execution environments."""
src/openenv.egg-info/PKG-INFO ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: openenv
3
+ Version: 0.2.0
4
+ Summary: A unified framework for reinforcement learning environments
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: fastapi>=0.104.0
9
+ Requires-Dist: pydantic>=2.0.0
10
+ Requires-Dist: uvicorn>=0.24.0
11
+ Requires-Dist: requests>=2.25.0
12
+ Requires-Dist: typer>=0.9.0
13
+ Requires-Dist: rich>=13.0.0
14
+ Requires-Dist: pyyaml>=6.0
15
+ Requires-Dist: huggingface_hub>=0.20.0
16
+ Requires-Dist: openai>=2.7.2
17
+ Requires-Dist: tomli>=2.3.0
18
+ Requires-Dist: tomli-w>=1.2.0
19
+ Requires-Dist: websockets>=15.0.1
20
+ Provides-Extra: core
21
+ Requires-Dist: fastapi>=0.104.0; extra == "core"
22
+ Requires-Dist: pydantic>=2.0.0; extra == "core"
23
+ Requires-Dist: uvicorn>=0.24.0; extra == "core"
24
+ Requires-Dist: requests>=2.25.0; extra == "core"
25
+ Requires-Dist: websockets>=15.0.1; extra == "core"
26
+ Provides-Extra: cli
27
+ Requires-Dist: typer>=0.9.0; extra == "cli"
28
+ Requires-Dist: rich>=13.0.0; extra == "cli"
29
+ Requires-Dist: pyyaml>=6.0; extra == "cli"
30
+ Requires-Dist: huggingface_hub>=0.20.0; extra == "cli"
31
+ Requires-Dist: openai>=2.7.2; extra == "cli"
32
+ Requires-Dist: tomli>=2.3.0; extra == "cli"
33
+ Requires-Dist: tomli-w>=1.2.0; extra == "cli"
34
+ Provides-Extra: all
35
+ Requires-Dist: openenv[core]; extra == "all"
36
+ Requires-Dist: openenv[cli]; extra == "all"
37
+ Dynamic: license-file
38
+
39
+ # <img width="35" height="35" alt="image" src="https://github.com/user-attachments/assets/2700a971-e5d6-4036-b03f-2f89c9791609" /> OpenEnv: Agentic Execution Environments
40
+
41
+ An e2e framework for creating, deploying and using isolated execution environments for agentic RL training, built using Gymnasium style simple APIs.
42
+
43
+ [![PyPI](https://img.shields.io/pypi/v/openenv?color=blue)](https://pypi.org/project/openenv/)
44
+ [![Discord](https://img.shields.io/badge/Discord-OpenEnv-7289da?style=flat&logo=discord&logoColor=white)](https://discord.gg/YsTYBh6PD9)
45
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/meta-pytorch/OpenEnv/blob/main/examples/OpenEnv_Tutorial.ipynb)
46
+ [![Docs](https://img.shields.io/badge/Docs-Explore-blue?logo=readthedocs&logoColor=white)](https://meta-pytorch.org/OpenEnv/)
47
+
48
+ ---
49
+
50
+ **🚀 Featured Example:** Train LLMs to play BlackJack using [torchforge](https://github.com/meta-pytorch/torchforge) (PyTorch's agentic RL framework): [`examples/grpo_blackjack/`](examples/grpo_blackjack/)
51
+
52
+ ## OpenEnv on partner platforms:
53
+
54
+ - [Lightning AI Studio](https://lightning.ai/environments?section=featured)
55
+ - [TRL example](https://huggingface.co/docs/trl/main/en/openenv)
56
+ - [Unsloth Google Colab](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/OpenEnv_gpt_oss_(20B)_Reinforcement_Learning_2048_Game.ipynb)
57
+ - [ART example](https://art.openpipe.ai/integrations/openenv-integration)
58
+ - [Oumi example](https://github.com/oumi-ai/oumi/blob/main/notebooks/Oumi%20-%20OpenEnv%20GRPO%20with%20trl.ipynb)
59
+
60
+ ## Overview
61
+
62
+ 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.
63
+
64
+ 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 familiar 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.
65
+
66
+ The OpenEnv CLI (`openenv`) provides commands to initialize new environments and deploy them to Hugging Face Spaces.
67
+
68
+ > ⚠️ **Early Development Warning** OpenEnv is currently in an experimental
69
+ > stage. You should expect bugs, incomplete features, and APIs that may change
70
+ > in future versions. The project welcomes bugfixes, but to make sure things are
71
+ > well coordinated you should discuss any significant change before starting the
72
+ > work. It's recommended that you signal your intention to contribute in the
73
+ > issue tracker, either by filing a new issue or by claiming an existing one.
74
+
75
+ ### RFCs
76
+
77
+ Below is a list of active and historical RFCs for OpenEnv. RFCs are proposals for major changes or features. Please review and contribute!
78
+
79
+ - [RFC 001: Baseline API and Interface Specifications](https://github.com/meta-pytorch/OpenEnv/pull/26)
80
+
81
+ ## Architecture
82
+
83
+ ### Component Overview
84
+
85
+ ```
86
+ ┌─────────────────────────────────────────────────────────┐
87
+ │ Client Application │
88
+ │ ┌────────────────┐ ┌──────────────────┐ │
89
+ │ │ EchoEnv │ │ CodingEnv │ │
90
+ │ │ (HTTPEnvClient)│ �� (HTTPEnvClient) │ │
91
+ │ └────────┬───────┘ └────────┬─────────┘ │
92
+ └───────────┼───────────────────────────────┼─────────────┘
93
+ │ HTTP │ HTTP
94
+ │ (reset, step, state) │
95
+ ┌───────────▼───────────────────────────────▼─────────────┐
96
+ │ Docker Containers (Isolated) │
97
+ │ ┌──────────────────────┐ ┌──────────────────────┐ │
98
+ │ │ FastAPI Server │ │ FastAPI Server │ │
99
+ │ │ EchoEnvironment │ │ PythonCodeActEnv │ │
100
+ │ │ (Environment base) │ │ (Environment base) │ │
101
+ │ └──────────────────────┘ └──────────────────────┘ │
102
+ └─────────────────────────────────────────────────────────┘
103
+ ```
104
+
105
+ ### Core Components
106
+
107
+ #### 1. Web Interface
108
+
109
+ OpenEnv includes a built-in web interface for interactive environment exploration and debugging. The web interface provides:
110
+
111
+ - **Two-Pane Layout**: HumanAgent interaction on the left, state observation on the right
112
+ - **Real-time Updates**: WebSocket-based live updates without page refresh
113
+ - **Dynamic Forms**: Automatically generated action forms based on environment Action types
114
+ - **Action History**: Complete log of all actions taken and their results
115
+
116
+ The web interface is **conditionally enabled** based on environment variables:
117
+
118
+ - **Local Development**: Disabled by default for lightweight development
119
+ - **Manual Override**: Enable with `ENABLE_WEB_INTERFACE=true`
120
+
121
+ To use the web interface:
122
+
123
+ ```python
124
+ from openenv.core.env_server import create_web_interface_app
125
+ from your_env.models import YourAction, YourObservation
126
+ from your_env.server.your_environment import YourEnvironment
127
+
128
+ env = YourEnvironment()
129
+ app = create_web_interface_app(env, YourAction, YourObservation)
130
+ ```
131
+
132
+ When enabled, open `http://localhost:8000/web` in your browser to interact with the environment.
133
+
134
+ #### 2. Environment (Server-Side)
135
+ Base class for implementing environment logic:
136
+ - **`reset()`**: Initialize a new episode, returns initial `Observation`
137
+ - **`step(action)`**: Execute an `Action`, returns resulting `Observation`
138
+ - **`state()`**: Access episode metadata (`State` with episode_id, step_count, etc.)
139
+
140
+ #### 3. HTTPEnvClient (Client-Side)
141
+ Base class for HTTP communication:
142
+ - Handles HTTP requests to environment server
143
+ - Contains a utility to spin up a docker container locally for the corresponding environment
144
+ - Type-safe action/observation parsing
145
+
146
+ #### 4. Container Providers
147
+ Manage container deployment:
148
+ - `LocalDockerProvider`: Run containers on local Docker daemon
149
+ - `KubernetesProvider`: Deploy to K8s clusters (future)
150
+
151
+ #### 5. Models
152
+ Type-safe data structures:
153
+ - `Action`: Base class for environment actions
154
+ - `Observation`: Base class for environment observations
155
+ - `State`: Episode state tracking
156
+ - `StepResult`: Combines observation, reward, done flag
157
+
158
+ ## Project Structure
159
+
160
+ ### For Environment Creators
161
+
162
+ Use the CLI to quickly scaffold a new environment:
163
+
164
+ ```bash
165
+ openenv init my_env
166
+ ```
167
+
168
+ This creates the following structure:
169
+
170
+ ```
171
+ my_env/
172
+ ├── .dockerignore # Docker build exclusions
173
+ ├── __init__.py # Export YourAction, YourObservation, YourEnv
174
+ ├── models.py # Define Action, Observation, State dataclasses
175
+ ├── client.py # Implement YourEnv(HTTPEnvClient)
176
+ ├── README.md # Document your environment
177
+ ├── openenv.yaml # Environment manifest
178
+ ├── pyproject.toml # Dependencies and package configuration
179
+ ├── outputs/ # Runtime outputs (logs, evals) - gitignored
180
+ │ ├── logs/
181
+ │ └── evals/
182
+ └── server/
183
+ ├── your_environment.py # Implement YourEnvironment(Environment)
184
+ ├── app.py # Create FastAPI app
185
+ ├── requirements.txt # Dependencies for Docker (can be generated)
186
+ └── Dockerfile # Define container image
187
+ ```
188
+
189
+ #### Dependency Management
190
+
191
+ OpenEnv uses `pyproject.toml` as the primary dependency specification:
192
+
193
+ - **Environment-level `pyproject.toml`**: Each environment defines its own dependencies
194
+ - **Root-level `pyproject.toml`**: Contains shared core dependencies (fastapi, pydantic, uvicorn)
195
+ - **Server `requirements.txt`**: Can be auto-generated from `pyproject.toml` for Docker builds
196
+
197
+ **Development Workflow:**
198
+
199
+ ```bash
200
+ # Install environment in editable mode
201
+ cd my_env
202
+ pip install -e .
203
+
204
+ # Or using uv (faster)
205
+ uv pip install -e .
206
+
207
+ # Run server locally without Docker
208
+ uv run server --host 0.0.0.0 --port 8000
209
+ ```
210
+
211
+ **Benefits:**
212
+ - ✅ **Client-side extensions**: Modify client classes locally without repo changes
213
+ - ✅ **Better dependency management**: Clear separation between environments
214
+ - ✅ **Flexible workflows**: Use pip, uv, or Docker for different scenarios
215
+ - ✅ **CI/CD ready**: Automated dependency generation and validation
216
+
217
+ See [`envs/README.md`](envs/README.md) for a complete guide on building environments.
218
+
219
+ ### For Environment Users
220
+
221
+ To use an environment:
222
+ 1. Import from `envs.your_env`: `from envs.echo_env import EchoAction, EchoEnv`
223
+ 2. Create client: `client = EchoEnv.from_docker_image("echo-env:latest")`
224
+ 3. Interact: `client.reset()`, `client.step(action)`, `client.state()`
225
+ 4. Cleanup: `client.close()`
226
+
227
+ See example scripts in `examples/` directory.
228
+
229
+ ## CLI Commands
230
+
231
+ The OpenEnv CLI provides commands to manage environments:
232
+
233
+ - **`openenv init <env_name>`** - Initialize a new environment from template
234
+ - **`openenv push [--repo-id <repo>] [--private]`** - Deploy environment to Hugging Face Spaces
235
+
236
+ ### Quick Start
237
+
238
+ ```bash
239
+ # Create a new environment
240
+ openenv init my_game_env
241
+
242
+ # Deploy to Hugging Face (will prompt for login if needed)
243
+ cd my_game_env
244
+ openenv push
245
+ ```
246
+
247
+ For detailed options: `openenv init --help` and `openenv push --help`.
248
+
249
+ ## Design Principles
250
+
251
+ 1. **Separation of Concerns**: Clear client-server boundaries
252
+ 2. **Type Safety**: Strongly-typed actions, observations, and state
253
+ 3. **Container Isolation**: Each environment runs in its own container
254
+ 4. **Simple APIs**: Minimal, intuitive interfaces
255
+
256
+ ## Quick Start
257
+
258
+ ### Using the Echo Environment(Example)
259
+
260
+ ```python
261
+ from envs.echo_env import EchoAction, EchoEnv
262
+
263
+ # Automatically start container and connect
264
+ client = EchoEnv.from_docker_image("echo-env:latest")
265
+
266
+ # Reset the environment
267
+ result = client.reset()
268
+ print(result.observation.echoed_message) # "Echo environment ready!"
269
+
270
+ # Send messages
271
+ result = client.step(EchoAction(message="Hello, World!"))
272
+ print(result.observation.echoed_message) # "Hello, World!"
273
+ print(result.reward) # 1.3 (based on message length)
274
+
275
+ # Cleanup
276
+ client.close() # Stops and removes container
277
+ ```
278
+
279
+ ## Requirements
280
+
281
+ - Python 3.11+
282
+ - Docker Desktop or Docker Engine
283
+ - FastAPI >= 0.104.0
284
+ - Uvicorn >= 0.24.0
285
+ - Requests >= 2.25.0
286
+ - smolagents (for coding environment)
287
+
288
+ ## Supported RL Tools
289
+ The goal of this project is to support a broad set of open and closed tools to help standardize the agentic RL community. If you have a project that supports OpenEnv environments, please put up a PR to add your tool name along with a link to your documentation.
290
+
291
+ ### torchforge
292
+ See GRPO BlackJack training example: [`examples/grpo_blackjack/`](examples/grpo_blackjack/)
293
+
294
+ ### TRL
295
+ See the [TRL example](https://huggingface.co/docs/trl/main/en/openenv) on how to integrate OpenEnv environments with GRPO training.
296
+
297
+ ### Unsloth
298
+ See the 2048 game example based on gpt-oss: [Colab notebook](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/OpenEnv_gpt_oss_(20B)_Reinforcement_Learning_2048_Game.ipynb)
299
+
300
+ ### SkyRL
301
+ See the [SkyRL example](https://skyrl.readthedocs.io/en/latest/examples/openenv.html) on how to train on OpenEnv environments with SkyRL.
302
+
303
+ ### ART
304
+ See the [ART example](https://art.openpipe.ai/integrations/openenv-integration) on how OpenEnv environments can be used to train models with ART.
305
+
306
+ ### Oumi
307
+ See the [Oumi example](https://github.com/oumi-ai/oumi/blob/main/notebooks/Oumi%20-%20OpenEnv%20GRPO%20with%20trl.ipynb) on how OpenEnv environments can be used to train models with Oumi.
308
+
309
+ ## Example Environments
310
+
311
+ ### Echo Environment
312
+ A simple environment that echoes back messages with metadata. Perfect for:
313
+ - Testing the HTTP server infrastructure
314
+ - Learning the framework basics
315
+ - Verifying container deployment
316
+
317
+ See: [`envs/echo_env/README.md`](envs/echo_env/README.md)
318
+
319
+ ### Coding Environment
320
+ Executes arbitrary Python code in a sandboxed environment. Features:
321
+ - Safe code execution using smolagents
322
+ - Capture stdout, stderr, and exit codes
323
+ - Persistent execution context within episodes
324
+ - Error handling with detailed messages
325
+
326
+ See: [`envs/coding_env/README.md`](envs/coding_env/README.md)
327
+
328
+ ## Community Support & Acknowledgments
329
+ This is an open and community-centric project. If you would like to add your name here, please put up a pull request and tag @jspisak for review. Ty!!
330
+
331
+ Supporters include: Meta-PyTorch, Hugging Face, [Patronus AI](https://patronus.ai), [Surge AI](https://surgehq.ai), [LastMile AI](https://www.lastmileai.dev), Unsloth AI, Reflection AI, vLLM, SkyRL (UC-Berkeley), LightningAI, Axolotl AI, Stanford Scaling Intelligence Lab, Mithril, [OpenMined](https://openmined.org/), [Fleet AI](https://fleetai.com), [Halluminate](https://halluminate.ai/), [Turing](https://www.turing.com/) ..
332
+
333
+ And we'd also like to acknowledge the team at Farama Foundation as the OpenEnv API was heavily inspired by the work you all have done on Gymnasium. Cheers!
334
+
335
+ ## License
336
+
337
+ BSD 3-Clause License (see [LICENSE](./LICENSE) file)
src/openenv.egg-info/SOURCES.txt ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ envs/atari_env/__init__.py
5
+ envs/atari_env/client.py
6
+ envs/atari_env/models.py
7
+ envs/atari_env/server/__init__.py
8
+ envs/atari_env/server/app.py
9
+ envs/atari_env/server/atari_environment.py
10
+ envs/browsergym_env/__init__.py
11
+ envs/browsergym_env/client.py
12
+ envs/browsergym_env/models.py
13
+ envs/browsergym_env/server/__init__.py
14
+ envs/browsergym_env/server/app.py
15
+ envs/browsergym_env/server/browsergym_environment.py
16
+ envs/chat_env/__init__.py
17
+ envs/chat_env/client.py
18
+ envs/chat_env/models.py
19
+ envs/chat_env/server/__init__.py
20
+ envs/chat_env/server/app.py
21
+ envs/chat_env/server/chat_environment.py
22
+ envs/chat_env/server/test_chat_env.py
23
+ envs/coding_env/__init__.py
24
+ envs/coding_env/client.py
25
+ envs/coding_env/models.py
26
+ envs/coding_env/server/__init__.py
27
+ envs/coding_env/server/app.py
28
+ envs/coding_env/server/python_codeact_env.py
29
+ envs/coding_env/server/python_executor.py
30
+ envs/coding_env/server/transforms.py
31
+ envs/connect4_env/__init__.py
32
+ envs/connect4_env/client.py
33
+ envs/connect4_env/models.py
34
+ envs/connect4_env/server/__init__.py
35
+ envs/connect4_env/server/app.py
36
+ envs/connect4_env/server/connect4_environment.py
37
+ envs/dipg_safety_env/__init__.py
38
+ envs/dipg_safety_env/client.py
39
+ envs/dipg_safety_env/models.py
40
+ envs/dipg_safety_env/server/__init__.py
41
+ envs/dipg_safety_env/server/app.py
42
+ envs/dipg_safety_env/server/dipg_environment.py
43
+ envs/echo_env/__init__.py
44
+ envs/echo_env/client.py
45
+ envs/echo_env/models.py
46
+ envs/echo_env/build/lib/server/__init__.py
47
+ envs/echo_env/build/lib/server/app.py
48
+ envs/echo_env/build/lib/server/echo_environment.py
49
+ envs/echo_env/server/__init__.py
50
+ envs/echo_env/server/app.py
51
+ envs/echo_env/server/echo_environment.py
52
+ envs/finrl_env/__init__.py
53
+ envs/finrl_env/client.py
54
+ envs/finrl_env/models.py
55
+ envs/finrl_env/server/__init__.py
56
+ envs/finrl_env/server/app.py
57
+ envs/finrl_env/server/finrl_environment.py
58
+ envs/git_env/__init__.py
59
+ envs/git_env/client.py
60
+ envs/git_env/models.py
61
+ envs/git_env/server/__init__.py
62
+ envs/git_env/server/app.py
63
+ envs/git_env/server/git_task_environment.py
64
+ envs/openspiel_env/__init__.py
65
+ envs/openspiel_env/client.py
66
+ envs/openspiel_env/models.py
67
+ envs/openspiel_env/server/__init__.py
68
+ envs/openspiel_env/server/app.py
69
+ envs/openspiel_env/server/openspiel_environment.py
70
+ envs/openspiel_env/server/opponent_policies.py
71
+ envs/play/build/lib/server/__init__.py
72
+ envs/play/build/lib/server/app.py
73
+ envs/play/build/lib/server/play_environment.py
74
+ envs/sumo_rl_env/__init__.py
75
+ envs/sumo_rl_env/client.py
76
+ envs/sumo_rl_env/models.py
77
+ envs/sumo_rl_env/server/__init__.py
78
+ envs/sumo_rl_env/server/app.py
79
+ envs/sumo_rl_env/server/sumo_environment.py
80
+ envs/textarena_env/__init__.py
81
+ envs/textarena_env/client.py
82
+ envs/textarena_env/models.py
83
+ envs/textarena_env/rewards.py
84
+ envs/textarena_env/build/lib/server/__init__.py
85
+ envs/textarena_env/build/lib/server/app.py
86
+ envs/textarena_env/build/lib/server/environment.py
87
+ envs/textarena_env/server/__init__.py
88
+ envs/textarena_env/server/app.py
89
+ envs/textarena_env/server/environment.py
90
+ src/openenv/__init__.py
91
+ src/openenv.egg-info/PKG-INFO
92
+ src/openenv.egg-info/SOURCES.txt
93
+ src/openenv.egg-info/dependency_links.txt
94
+ src/openenv.egg-info/entry_points.txt
95
+ src/openenv.egg-info/requires.txt
96
+ src/openenv.egg-info/top_level.txt
97
+ src/openenv/cli/__init__.py
98
+ src/openenv/cli/__main__.py
99
+ src/openenv/cli/_cli_utils.py
100
+ src/openenv/cli/_validation.py
101
+ src/openenv/cli/commands/__init__.py
102
+ src/openenv/cli/commands/build.py
103
+ src/openenv/cli/commands/init.py
104
+ src/openenv/cli/commands/push.py
105
+ src/openenv/cli/commands/serve.py
106
+ src/openenv/cli/commands/validate.py
107
+ src/openenv/cli/templates/__init__.py
108
+ src/openenv/cli/templates/__pycache__/__init__.cpython-311.pyc
109
+ src/openenv/cli/templates/__pycache__/__init__.cpython-313.pyc
110
+ src/openenv/cli/templates/openenv_env/README.md
111
+ src/openenv/cli/templates/openenv_env/__init__.py
112
+ src/openenv/cli/templates/openenv_env/client.py
113
+ src/openenv/cli/templates/openenv_env/models.py
114
+ src/openenv/cli/templates/openenv_env/openenv.yaml
115
+ src/openenv/cli/templates/openenv_env/pyproject.toml
116
+ src/openenv/cli/templates/openenv_env/server/Dockerfile
117
+ src/openenv/cli/templates/openenv_env/server/__ENV_NAME___environment.py
118
+ src/openenv/cli/templates/openenv_env/server/__init__.py
119
+ src/openenv/cli/templates/openenv_env/server/app.py
120
+ src/openenv/cli/templates/openenv_env/server/requirements.txt
121
+ src/openenv/core/__init__.py
122
+ src/openenv/core/client_types.py
123
+ src/openenv/core/env_client.py
124
+ src/openenv/core/utils.py
125
+ src/openenv/core/containers/__init__.py
126
+ src/openenv/core/containers/test_local_docker_provider.py
127
+ src/openenv/core/containers/runtime/__init__.py
128
+ src/openenv/core/containers/runtime/providers.py
129
+ src/openenv/core/containers/runtime/uv_provider.py
130
+ src/openenv/core/env_server/__init__.py
131
+ src/openenv/core/env_server/base_transforms.py
132
+ src/openenv/core/env_server/exceptions.py
133
+ src/openenv/core/env_server/http_server.py
134
+ src/openenv/core/env_server/interfaces.py
135
+ src/openenv/core/env_server/route_config.py
136
+ src/openenv/core/env_server/serialization.py
137
+ src/openenv/core/env_server/types.py
138
+ src/openenv/core/env_server/web_interface.py
139
+ src/openenv/core/tools/__init__.py
140
+ src/openenv/core/tools/git_server_client.py
141
+ src/openenv/core/tools/local_python_executor.py
142
+ src/openenv_core/__init__.py
src/openenv.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
src/openenv.egg-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ openenv = openenv.cli.__main__:main
src/openenv.egg-info/requires.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.104.0
2
+ pydantic>=2.0.0
3
+ uvicorn>=0.24.0
4
+ requests>=2.25.0
5
+ typer>=0.9.0
6
+ rich>=13.0.0
7
+ pyyaml>=6.0
8
+ huggingface_hub>=0.20.0
9
+ openai>=2.7.2
10
+ tomli>=2.3.0
11
+ tomli-w>=1.2.0
12
+ websockets>=15.0.1
13
+
14
+ [all]
15
+ openenv[core]
16
+ openenv[cli]
17
+
18
+ [cli]
19
+ typer>=0.9.0
20
+ rich>=13.0.0
21
+ pyyaml>=6.0
22
+ huggingface_hub>=0.20.0
23
+ openai>=2.7.2
24
+ tomli>=2.3.0
25
+ tomli-w>=1.2.0
26
+
27
+ [core]
28
+ fastapi>=0.104.0
29
+ pydantic>=2.0.0
30
+ uvicorn>=0.24.0
31
+ requests>=2.25.0
32
+ websockets>=15.0.1
src/openenv.egg-info/top_level.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ openenv
2
+ openenv_core