openenv 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. openenv-0.3.0/MANIFEST.in +2 -0
  2. {openenv-0.2.2/src/openenv.egg-info → openenv-0.3.0}/PKG-INFO +27 -17
  3. {openenv-0.2.2 → openenv-0.3.0}/README.md +22 -12
  4. {openenv-0.2.2 → openenv-0.3.0}/pyproject.toml +9 -5
  5. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/__init__.py +12 -4
  6. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/auto/_discovery.py +2 -2
  7. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/__main__.py +14 -1
  8. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/_validation.py +81 -9
  9. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/build.py +16 -7
  10. openenv-0.3.0/src/openenv/cli/commands/collect.py +443 -0
  11. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/push.py +211 -26
  12. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/skills.py +12 -5
  13. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/pyproject.toml +1 -1
  14. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/server/app.py +1 -1
  15. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/http_server.py +50 -6
  16. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/interfaces.py +5 -1
  17. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/mcp_environment.py +64 -91
  18. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/web_interface.py +100 -14
  19. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/evals/inspect_harness.py +13 -1
  20. openenv-0.3.0/src/openenv/core/harness/__init__.py +723 -0
  21. openenv-0.3.0/src/openenv/core/harness/collect.py +586 -0
  22. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/llm_client.py +32 -8
  23. {openenv-0.2.2 → openenv-0.3.0/src/openenv.egg-info}/PKG-INFO +27 -17
  24. {openenv-0.2.2 → openenv-0.3.0}/src/openenv.egg-info/SOURCES.txt +4 -1
  25. {openenv-0.2.2 → openenv-0.3.0}/src/openenv.egg-info/requires.txt +4 -4
  26. {openenv-0.2.2 → openenv-0.3.0}/LICENSE +0 -0
  27. {openenv-0.2.2 → openenv-0.3.0}/setup.cfg +0 -0
  28. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/auto/__init__.py +0 -0
  29. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/auto/auto_action.py +0 -0
  30. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/auto/auto_env.py +0 -0
  31. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/__init__.py +0 -0
  32. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/_cli_utils.py +0 -0
  33. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/__init__.py +0 -0
  34. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/fork.py +0 -0
  35. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/init.py +0 -0
  36. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/serve.py +0 -0
  37. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/commands/validate.py +0 -0
  38. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/__init__.py +0 -0
  39. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/README.md +0 -0
  40. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/__init__.py +0 -0
  41. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/client.py +0 -0
  42. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/models.py +0 -0
  43. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/openenv.yaml +0 -0
  44. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/server/Dockerfile +0 -0
  45. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/server/__ENV_NAME___environment.py +0 -0
  46. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/server/__init__.py +0 -0
  47. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/cli/templates/openenv_env/server/requirements.txt +0 -0
  48. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/__init__.py +0 -0
  49. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/client_types.py +0 -0
  50. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/containers/__init__.py +0 -0
  51. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/containers/runtime/__init__.py +0 -0
  52. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/containers/runtime/daytona_provider.py +0 -0
  53. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/containers/runtime/providers.py +0 -0
  54. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/containers/runtime/uv_provider.py +0 -0
  55. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/containers/test_local_docker_provider.py +0 -0
  56. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_client.py +0 -0
  57. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/__init__.py +0 -0
  58. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/base_transforms.py +0 -0
  59. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/exceptions.py +0 -0
  60. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/gradio_theme.py +0 -0
  61. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/gradio_ui.py +0 -0
  62. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/mcp_types.py +0 -0
  63. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/route_config.py +0 -0
  64. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/serialization.py +0 -0
  65. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/env_server/types.py +0 -0
  66. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/evals/__init__.py +0 -0
  67. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/evals/base.py +0 -0
  68. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/evals/types.py +0 -0
  69. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/generic_client.py +0 -0
  70. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/mcp_client.py +0 -0
  71. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/rubrics/__init__.py +0 -0
  72. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/rubrics/base.py +0 -0
  73. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/rubrics/containers.py +0 -0
  74. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/rubrics/llm_judge.py +0 -0
  75. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/rubrics/trajectory.py +0 -0
  76. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/sync_client.py +0 -0
  77. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/tools/__init__.py +0 -0
  78. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/tools/git_server_client.py +0 -0
  79. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/tools/local_python_executor.py +0 -0
  80. {openenv-0.2.2 → openenv-0.3.0}/src/openenv/core/utils.py +0 -0
  81. {openenv-0.2.2 → openenv-0.3.0}/src/openenv.egg-info/dependency_links.txt +0 -0
  82. {openenv-0.2.2 → openenv-0.3.0}/src/openenv.egg-info/entry_points.txt +0 -0
  83. {openenv-0.2.2 → openenv-0.3.0}/src/openenv.egg-info/top_level.txt +0 -0
  84. {openenv-0.2.2 → openenv-0.3.0}/src/openenv_core/__init__.py +0 -0
  85. {openenv-0.2.2 → openenv-0.3.0}/tests/test_line_endings.py +0 -0
@@ -0,0 +1,2 @@
1
+ global-exclude *.py[cod]
2
+ global-exclude __pycache__/*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openenv
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A unified framework for reinforcement learning environments
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -37,13 +37,13 @@ Requires-Dist: tomli-w>=1.2.0; extra == "cli"
37
37
  Provides-Extra: docs
38
38
  Requires-Dist: sphinx==7.2.6; extra == "docs"
39
39
  Requires-Dist: pytorch-sphinx-theme2; extra == "docs"
40
- Requires-Dist: sphinxcontrib.katex==0.9.10; extra == "docs"
41
- Requires-Dist: docutils<0.21,>=0.18.1; extra == "docs"
40
+ Requires-Dist: sphinxcontrib.katex==0.9.11; extra == "docs"
41
+ Requires-Dist: docutils<0.23,>=0.18.1; extra == "docs"
42
42
  Requires-Dist: sphinx-design==0.6.1; extra == "docs"
43
- Requires-Dist: sphinxcontrib-mermaid==1.0.0; extra == "docs"
43
+ Requires-Dist: sphinxcontrib-mermaid==2.0.2; extra == "docs"
44
44
  Requires-Dist: myst-parser; extra == "docs"
45
45
  Requires-Dist: sphinxext-opengraph; extra == "docs"
46
- Requires-Dist: sphinx-sitemap==2.7.1; extra == "docs"
46
+ Requires-Dist: sphinx-sitemap==2.9.0; extra == "docs"
47
47
  Requires-Dist: sphinx-gallery>=0.14.0; extra == "docs"
48
48
  Requires-Dist: matplotlib; extra == "docs"
49
49
  Requires-Dist: nest-asyncio; extra == "docs"
@@ -75,7 +75,7 @@ An e2e framework for creating, deploying and using isolated execution environmen
75
75
 
76
76
  ## Quick Start
77
77
 
78
- Install the OpenEnv core package:
78
+ Install the OpenEnv package:
79
79
 
80
80
  ```bash
81
81
  pip install openenv
@@ -91,7 +91,7 @@ Then use the environment:
91
91
 
92
92
  ```python
93
93
  import asyncio
94
- from echo_env import EchoAction, EchoEnv
94
+ from echo_env import CallToolAction, EchoEnv
95
95
 
96
96
  async def main():
97
97
  # Connect to a running Space (async context manager)
@@ -101,9 +101,14 @@ async def main():
101
101
  print(result.observation.echoed_message) # "Echo environment ready!"
102
102
 
103
103
  # Send messages
104
- result = await client.step(EchoAction(message="Hello, World!"))
105
- print(result.observation.echoed_message) # "Hello, World!"
106
- print(result.reward) # 1.3 (based on message length)
104
+ result = await client.step(
105
+ CallToolAction(
106
+ tool_name="echo_message",
107
+ arguments={"message": "Hello, World!"},
108
+ )
109
+ )
110
+ print(result.observation.result) # "Hello, World!"
111
+ print(result.reward)
107
112
 
108
113
  asyncio.run(main())
109
114
  ```
@@ -111,16 +116,21 @@ asyncio.run(main())
111
116
  **Synchronous usage** is also supported via the `.sync()` wrapper:
112
117
 
113
118
  ```python
114
- from echo_env import EchoAction, EchoEnv
119
+ from echo_env import CallToolAction, EchoEnv
115
120
 
116
121
  # Use .sync() for synchronous context manager
117
122
  with EchoEnv(base_url="https://openenv-echo-env.hf.space").sync() as client:
118
123
  result = client.reset()
119
- result = client.step(EchoAction(message="Hello, World!"))
120
- print(result.observation.echoed_message)
124
+ result = client.step(
125
+ CallToolAction(
126
+ tool_name="echo_message",
127
+ arguments={"message": "Hello, World!"},
128
+ )
129
+ )
130
+ print(result.observation.result)
121
131
  ```
122
132
 
123
- For a detailed quick start, check out the [docs page](https://meta-pytorch.org/OpenEnv/quickstart/).
133
+ For a detailed quick start, check out the [docs page](https://meta-pytorch.org/OpenEnv/auto_getting_started/index.html).
124
134
 
125
135
  ## OpenEnv on partner platforms:
126
136
 
@@ -299,7 +309,7 @@ See [`envs/README.md`](envs/README.md) for a complete guide on building environm
299
309
 
300
310
  To use an environment:
301
311
  1. Install the client: `pip install git+https://huggingface.co/spaces/openenv/echo-env`
302
- 2. Import: `from echo_env import EchoAction, EchoEnv`
312
+ 2. Import: `from echo_env import CallToolAction, EchoEnv`
303
313
  3. Use async (recommended) or sync API:
304
314
 
305
315
  **Async (recommended):**
@@ -427,12 +437,12 @@ See the [Oumi example](https://github.com/oumi-ai/oumi/blob/main/notebooks/Oumi%
427
437
  | [Atari Environment](envs/atari_env/README.md) | Classic Arcade Learning Environment tasks for RL benchmarking. |
428
438
  | [FinRL Environment](envs/finrl_env/README.md) | Financial market simulations for algorithmic trading experiments. |
429
439
 
430
- > Browse the full catalog of community environments at [meta-pytorch.org/OpenEnv/environments](https://meta-pytorch.org/OpenEnv/environments/).
440
+ > Browse the full catalog of community environments at [meta-pytorch.org/OpenEnv/environments](https://meta-pytorch.org/OpenEnv/environments.html).
431
441
 
432
442
  ## Community Support & Acknowledgments
433
443
  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!!
434
444
 
435
- Supporters include: Meta-PyTorch, Hugging Face, [Scaler AI Labs](https://scalerailabs.com), [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/), [Scale AI](https://scale.com/) ..
445
+ Supporters include: Meta-PyTorch, Hugging Face, [Scaler AI Labs](https://scalerailabs.com), [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/), [Scale AI](https://scale.com/), [Scorecard](https://www.scorecard.io/) ..
436
446
 
437
447
  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!
438
448
 
@@ -15,7 +15,7 @@ An e2e framework for creating, deploying and using isolated execution environmen
15
15
 
16
16
  ## Quick Start
17
17
 
18
- Install the OpenEnv core package:
18
+ Install the OpenEnv package:
19
19
 
20
20
  ```bash
21
21
  pip install openenv
@@ -31,7 +31,7 @@ Then use the environment:
31
31
 
32
32
  ```python
33
33
  import asyncio
34
- from echo_env import EchoAction, EchoEnv
34
+ from echo_env import CallToolAction, EchoEnv
35
35
 
36
36
  async def main():
37
37
  # Connect to a running Space (async context manager)
@@ -41,9 +41,14 @@ async def main():
41
41
  print(result.observation.echoed_message) # "Echo environment ready!"
42
42
 
43
43
  # Send messages
44
- result = await client.step(EchoAction(message="Hello, World!"))
45
- print(result.observation.echoed_message) # "Hello, World!"
46
- print(result.reward) # 1.3 (based on message length)
44
+ result = await client.step(
45
+ CallToolAction(
46
+ tool_name="echo_message",
47
+ arguments={"message": "Hello, World!"},
48
+ )
49
+ )
50
+ print(result.observation.result) # "Hello, World!"
51
+ print(result.reward)
47
52
 
48
53
  asyncio.run(main())
49
54
  ```
@@ -51,16 +56,21 @@ asyncio.run(main())
51
56
  **Synchronous usage** is also supported via the `.sync()` wrapper:
52
57
 
53
58
  ```python
54
- from echo_env import EchoAction, EchoEnv
59
+ from echo_env import CallToolAction, EchoEnv
55
60
 
56
61
  # Use .sync() for synchronous context manager
57
62
  with EchoEnv(base_url="https://openenv-echo-env.hf.space").sync() as client:
58
63
  result = client.reset()
59
- result = client.step(EchoAction(message="Hello, World!"))
60
- print(result.observation.echoed_message)
64
+ result = client.step(
65
+ CallToolAction(
66
+ tool_name="echo_message",
67
+ arguments={"message": "Hello, World!"},
68
+ )
69
+ )
70
+ print(result.observation.result)
61
71
  ```
62
72
 
63
- For a detailed quick start, check out the [docs page](https://meta-pytorch.org/OpenEnv/quickstart/).
73
+ For a detailed quick start, check out the [docs page](https://meta-pytorch.org/OpenEnv/auto_getting_started/index.html).
64
74
 
65
75
  ## OpenEnv on partner platforms:
66
76
 
@@ -239,7 +249,7 @@ See [`envs/README.md`](envs/README.md) for a complete guide on building environm
239
249
 
240
250
  To use an environment:
241
251
  1. Install the client: `pip install git+https://huggingface.co/spaces/openenv/echo-env`
242
- 2. Import: `from echo_env import EchoAction, EchoEnv`
252
+ 2. Import: `from echo_env import CallToolAction, EchoEnv`
243
253
  3. Use async (recommended) or sync API:
244
254
 
245
255
  **Async (recommended):**
@@ -367,12 +377,12 @@ See the [Oumi example](https://github.com/oumi-ai/oumi/blob/main/notebooks/Oumi%
367
377
  | [Atari Environment](envs/atari_env/README.md) | Classic Arcade Learning Environment tasks for RL benchmarking. |
368
378
  | [FinRL Environment](envs/finrl_env/README.md) | Financial market simulations for algorithmic trading experiments. |
369
379
 
370
- > Browse the full catalog of community environments at [meta-pytorch.org/OpenEnv/environments](https://meta-pytorch.org/OpenEnv/environments/).
380
+ > Browse the full catalog of community environments at [meta-pytorch.org/OpenEnv/environments](https://meta-pytorch.org/OpenEnv/environments.html).
371
381
 
372
382
  ## Community Support & Acknowledgments
373
383
  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!!
374
384
 
375
- Supporters include: Meta-PyTorch, Hugging Face, [Scaler AI Labs](https://scalerailabs.com), [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/), [Scale AI](https://scale.com/) ..
385
+ Supporters include: Meta-PyTorch, Hugging Face, [Scaler AI Labs](https://scalerailabs.com), [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/), [Scale AI](https://scale.com/), [Scorecard](https://www.scorecard.io/) ..
376
386
 
377
387
  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!
378
388
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "openenv"
7
- version = "0.2.2"
7
+ version = "0.3.0"
8
8
  description = "A unified framework for reinforcement learning environments"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -52,13 +52,13 @@ cli = [
52
52
  docs = [
53
53
  "sphinx==7.2.6",
54
54
  "pytorch-sphinx-theme2",
55
- "sphinxcontrib.katex==0.9.10",
56
- "docutils>=0.18.1,<0.21",
55
+ "sphinxcontrib.katex==0.9.11",
56
+ "docutils>=0.18.1,<0.23",
57
57
  "sphinx-design==0.6.1",
58
- "sphinxcontrib-mermaid==1.0.0",
58
+ "sphinxcontrib-mermaid==2.0.2",
59
59
  "myst-parser",
60
60
  "sphinxext-opengraph",
61
- "sphinx-sitemap==2.7.1",
61
+ "sphinx-sitemap==2.9.0",
62
62
  "sphinx-gallery>=0.14.0",
63
63
  "matplotlib",
64
64
  "nest-asyncio",
@@ -86,8 +86,12 @@ include-package-data = true
86
86
  [tool.setuptools.package-data]
87
87
  "openenv.cli" = ["templates/**/*"]
88
88
 
89
+ [tool.setuptools.exclude-package-data]
90
+ "*" = ["*.pyc", "*.pyo", "__pycache__/*"]
91
+
89
92
  [tool.setuptools.packages.find]
90
93
  where = ["src"]
94
+ exclude = ["*.__pycache__", "*.__pycache__.*"]
91
95
 
92
96
  [tool.coverage.run]
93
97
  omit = [
@@ -14,10 +14,18 @@ __all__ = [
14
14
  "SyncEnvClient",
15
15
  ]
16
16
 
17
- try:
18
- __version__ = metadata.version("openenv") # type: ignore[arg-type]
19
- except metadata.PackageNotFoundError: # pragma: no cover - local dev
20
- __version__ = "0.0.0"
17
+
18
+ def _load_package_version() -> str:
19
+ """Resolve the installed distribution version for the OpenEnv package."""
20
+ for distribution_name in ("openenv", "openenv-core"):
21
+ try:
22
+ return metadata.version(distribution_name)
23
+ except metadata.PackageNotFoundError:
24
+ continue
25
+ return "0.0.0"
26
+
27
+
28
+ __version__ = _load_package_version()
21
29
 
22
30
 
23
31
  _LAZY_MODULES = {
@@ -369,14 +369,14 @@ class EnvironmentDiscovery:
369
369
  logger.warning(f"Failed to get installed packages: {e}")
370
370
  return environments
371
371
 
372
- # Filter for openenv-* packages (exclude openenv)
372
+ # Filter for openenv-* environment packages; ignore the legacy core wheel.
373
373
  for dist in distributions:
374
374
  package_name = dist.metadata["Name"]
375
375
 
376
376
  if not package_name.startswith("openenv-"):
377
377
  continue
378
378
 
379
- if package_name == "openenv":
379
+ if package_name == "openenv-core":
380
380
  continue
381
381
 
382
382
  # Get module name (e.g., "openenv-echo_env" → "echo_env")
@@ -14,7 +14,16 @@ following the Hugging Face CLI pattern.
14
14
  import sys
15
15
 
16
16
  import typer
17
- from openenv.cli.commands import build, fork, init, push, serve, skills, validate
17
+ from openenv.cli.commands import (
18
+ build,
19
+ collect,
20
+ fork,
21
+ init,
22
+ push,
23
+ serve,
24
+ skills,
25
+ validate,
26
+ )
18
27
 
19
28
  # Create the main CLI app
20
29
  app = typer.Typer(
@@ -47,6 +56,10 @@ app.add_typer(
47
56
  name="skills",
48
57
  help="Manage OpenEnv skills for AI assistants",
49
58
  )
59
+ app.command(
60
+ name="collect",
61
+ help="Collect rollouts from a deployed OpenEnv environment",
62
+ )(collect.collect)
50
63
 
51
64
 
52
65
  # Entry point for setuptools
@@ -11,6 +11,8 @@ This module provides functions to check if environments are properly
11
11
  configured for multi-mode deployment (Docker, direct Python, notebooks, clusters).
12
12
  """
13
13
 
14
+ import ast
15
+ import re
14
16
  from pathlib import Path
15
17
  from typing import Any
16
18
  from urllib.parse import urlparse
@@ -426,6 +428,79 @@ def validate_running_environment(
426
428
  return report
427
429
 
428
430
 
431
+ def _has_main_guard_call(app_content: str) -> bool:
432
+ """Return True when the module calls main() under a __main__ guard."""
433
+ try:
434
+ tree = ast.parse(app_content)
435
+ except SyntaxError:
436
+ return (
437
+ "__name__" in app_content
438
+ and "__main__" in app_content
439
+ and "main(" in app_content
440
+ )
441
+
442
+ for node in ast.iter_child_nodes(tree):
443
+ if not isinstance(node, ast.If):
444
+ continue
445
+
446
+ test = node.test
447
+ if not (
448
+ isinstance(test, ast.Compare)
449
+ and isinstance(test.left, ast.Name)
450
+ and test.left.id == "__name__"
451
+ and len(test.ops) == 1
452
+ and isinstance(test.ops[0], ast.Eq)
453
+ and len(test.comparators) == 1
454
+ and isinstance(test.comparators[0], ast.Constant)
455
+ and test.comparators[0].value == "__main__"
456
+ ):
457
+ continue
458
+
459
+ for guarded_node in node.body:
460
+ for candidate in ast.walk(guarded_node):
461
+ if isinstance(candidate, ast.Call):
462
+ func = candidate.func
463
+ if isinstance(func, ast.Name) and func.id == "main":
464
+ return True
465
+
466
+ return False
467
+
468
+
469
+ _OPENENV_RUNTIME_DEP_RE = re.compile(r"^openenv(?:\s*(?:$|[<>=!~@;])|\[)")
470
+ _LEGACY_OPENENV_CORE_DEP_RE = re.compile(r"^openenv-core(?:\s*(?:$|[<>=!~@;])|\[)")
471
+ _OPENENV_DOCKER_INSTALL_RE = re.compile(
472
+ r"(?<![a-z0-9_.-])openenv(?:\s*(?:$|[<>=!~@;])|\[)"
473
+ )
474
+
475
+
476
+ def _dockerfile_installs_openenv_runtime(env_path: Path) -> bool:
477
+ """Return True when a Docker deployment installs OpenEnv outside pyproject."""
478
+ for dockerfile_path in (
479
+ env_path / "server" / "Dockerfile",
480
+ env_path / "Dockerfile",
481
+ ):
482
+ if not dockerfile_path.exists():
483
+ continue
484
+
485
+ try:
486
+ dockerfile = dockerfile_path.read_text(encoding="utf-8")
487
+ except OSError:
488
+ continue
489
+
490
+ for line in dockerfile.splitlines():
491
+ stripped = line.strip().lower()
492
+ if not stripped or stripped.startswith("#"):
493
+ continue
494
+ if _OPENENV_DOCKER_INSTALL_RE.search(stripped):
495
+ return True
496
+ if "openenv-core" in stripped:
497
+ return True
498
+ if "git+https://github.com/meta-pytorch/openenv" in stripped:
499
+ return True
500
+
501
+ return False
502
+
503
+
429
504
  def validate_multi_mode_deployment(env_path: Path) -> tuple[bool, list[str]]:
430
505
  """
431
506
  Validate that an environment is ready for multi-mode deployment.
@@ -475,15 +550,12 @@ def validate_multi_mode_deployment(env_path: Path) -> tuple[bool, list[str]]:
475
550
 
476
551
  # Check required dependencies
477
552
  deps = [dep.lower() for dep in pyproject.get("project", {}).get("dependencies", [])]
478
- has_openenv = any(
479
- dep.startswith("openenv") and not dep.startswith("openenv") for dep in deps
480
- )
481
- has_legacy_core = any(dep.startswith("openenv") for dep in deps)
553
+ has_openenv = any(_OPENENV_RUNTIME_DEP_RE.match(dep) for dep in deps)
554
+ has_legacy_core = any(_LEGACY_OPENENV_CORE_DEP_RE.match(dep) for dep in deps)
555
+ has_dockerfile_core = _dockerfile_installs_openenv_runtime(env_path)
482
556
 
483
- if not (has_openenv or has_legacy_core):
484
- issues.append(
485
- "Missing required dependency: openenv>=0.2.0 (or openenv>=0.2.0)"
486
- )
557
+ if not (has_openenv or has_legacy_core or has_dockerfile_core):
558
+ issues.append("Missing required dependency: openenv>=0.2.0")
487
559
 
488
560
  # Check server/app.py exists
489
561
  server_app = env_path / "server" / "app.py"
@@ -496,7 +568,7 @@ def validate_multi_mode_deployment(env_path: Path) -> tuple[bool, list[str]]:
496
568
  issues.append("server/app.py missing main() function")
497
569
 
498
570
  # Check if main() is callable
499
- if "__name__" not in app_content or "main()" not in app_content:
571
+ if not _has_main_guard_call(app_content):
500
572
  issues.append(
501
573
  "server/app.py main() function not callable (missing if __name__ == '__main__')"
502
574
  )
@@ -8,6 +8,7 @@
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
+ import re
11
12
  import shutil
12
13
  import subprocess
13
14
  import sys
@@ -21,6 +22,18 @@ from .._cli_utils import console
21
22
 
22
23
  app = typer.Typer(help="Build Docker images for OpenEnv environments")
23
24
 
25
+ _OPENENV_RUNTIME_DEP_RE = re.compile(r"^openenv(?:\s*(?:$|[<>=!~@;])|\[)")
26
+
27
+
28
+ def _is_openenv_runtime_dependency(dep: str) -> bool:
29
+ """Return True for the OpenEnv runtime distribution, not openenv-* envs."""
30
+ normalized = dep.strip().lower()
31
+ return (
32
+ _OPENENV_RUNTIME_DEP_RE.match(normalized) is not None
33
+ or normalized.startswith("openenv-core")
34
+ or normalized.startswith("openenv_core")
35
+ )
36
+
24
37
 
25
38
  def _detect_build_context(env_path: Path) -> tuple[str, Path, Path | None]:
26
39
  """
@@ -156,19 +169,15 @@ def _prepare_inrepo_build(env_path: Path, repo_root: Path, temp_dir: Path) -> Pa
156
169
  pyproject = tomli.load(f)
157
170
  deps = pyproject.get("project", {}).get("dependencies", [])
158
171
 
159
- # Replace openenv/openenv with local reference
172
+ # Replace OpenEnv package references with local source.
160
173
  new_deps = []
161
174
  for dep in deps:
162
- if (
163
- dep.startswith("openenv")
164
- or dep.startswith("openenv_core")
165
- or dep.startswith("openenv")
166
- ):
175
+ if _is_openenv_runtime_dependency(dep):
167
176
  # Skip - we'll use local core
168
177
  continue
169
178
  new_deps.append(dep)
170
179
 
171
- # Write back with local core reference
180
+ # Write back with local OpenEnv reference
172
181
  pyproject["project"]["dependencies"] = new_deps + [
173
182
  "openenv @ file:///app/env/openenv"
174
183
  ]