openenv 0.2.2__tar.gz → 0.2.3__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.
- openenv-0.2.3/MANIFEST.in +2 -0
- {openenv-0.2.2/src/openenv.egg-info → openenv-0.2.3}/PKG-INFO +1 -1
- {openenv-0.2.2 → openenv-0.2.3}/pyproject.toml +5 -1
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/__init__.py +12 -4
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/pyproject.toml +1 -1
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/interfaces.py +3 -1
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/mcp_environment.py +64 -91
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/web_interface.py +62 -9
- {openenv-0.2.2 → openenv-0.2.3/src/openenv.egg-info}/PKG-INFO +1 -1
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/SOURCES.txt +1 -0
- {openenv-0.2.2 → openenv-0.2.3}/LICENSE +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/README.md +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/setup.cfg +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/_discovery.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/auto_action.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/auto_env.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/__main__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/_cli_utils.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/_validation.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/build.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/fork.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/init.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/push.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/serve.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/skills.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/validate.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/README.md +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/client.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/models.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/openenv.yaml +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/Dockerfile +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/__ENV_NAME___environment.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/app.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/requirements.txt +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/client_types.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/daytona_provider.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/providers.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/uv_provider.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/test_local_docker_provider.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_client.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/base_transforms.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/exceptions.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/gradio_theme.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/gradio_ui.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/http_server.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/mcp_types.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/route_config.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/serialization.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/types.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/base.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/inspect_harness.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/types.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/generic_client.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/llm_client.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/mcp_client.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/base.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/containers.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/llm_judge.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/trajectory.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/sync_client.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/tools/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/tools/git_server_client.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/tools/local_python_executor.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/utils.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/dependency_links.txt +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/entry_points.txt +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/requires.txt +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/top_level.txt +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/src/openenv_core/__init__.py +0 -0
- {openenv-0.2.2 → openenv-0.2.3}/tests/test_line_endings.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openenv"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "A unified framework for reinforcement learning environments"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
def _load_package_version() -> str:
|
|
19
|
+
"""Resolve the installed distribution version for the OpenEnv package."""
|
|
20
|
+
for distribution_name in ("openenv", "openenv"):
|
|
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 = {
|
|
@@ -17,7 +17,7 @@ dependencies = [
|
|
|
17
17
|
# Core OpenEnv runtime (provides FastAPI server + HTTP client types)
|
|
18
18
|
# install from github
|
|
19
19
|
# "openenv[core] @ git+https://github.com/meta-pytorch/OpenEnv.git",
|
|
20
|
-
"openenv[core]>=0.2.
|
|
20
|
+
"openenv[core]>=0.2.2",
|
|
21
21
|
# Environment-specific dependencies
|
|
22
22
|
# Add all dependencies needed for your environment here
|
|
23
23
|
# Examples:
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import inspect
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
|
-
from typing import Any, Generic, Optional, Protocol, TYPE_CHECKING,
|
|
9
|
+
from typing import Any, Generic, Optional, Protocol, TYPE_CHECKING, TypeVar
|
|
10
|
+
|
|
11
|
+
from typing_extensions import TypedDict
|
|
10
12
|
|
|
11
13
|
from .types import Action, EnvironmentMetadata, Observation, State
|
|
12
14
|
|
|
@@ -420,24 +420,49 @@ class MCPEnvironment(Environment):
|
|
|
420
420
|
return self._step_impl(action, timeout_s=timeout_s, **kwargs)
|
|
421
421
|
|
|
422
422
|
def _handle_list_tools(self) -> ListToolsObservation:
|
|
423
|
+
"""Sync wrapper — delegates to the canonical async implementation."""
|
|
424
|
+
return run_async_safely(self._async_handle_list_tools())
|
|
425
|
+
|
|
426
|
+
async def _async_list_tools(self) -> list:
|
|
423
427
|
"""
|
|
424
|
-
|
|
428
|
+
Async helper to list tools from the MCP client.
|
|
425
429
|
|
|
426
430
|
Returns:
|
|
427
|
-
|
|
428
|
-
names, descriptions, and input schemas, filtered by current mode.
|
|
431
|
+
List of tool objects from the MCP server.
|
|
429
432
|
"""
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
current_mode = getattr(self, "_mode", None)
|
|
433
|
+
async with self.mcp_session() as client:
|
|
434
|
+
return await client.list_tools()
|
|
433
435
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
+
def _handle_call_tool(
|
|
437
|
+
self,
|
|
438
|
+
action: CallToolAction,
|
|
439
|
+
timeout_s: Optional[float] = None,
|
|
440
|
+
) -> CallToolObservation:
|
|
441
|
+
"""Sync wrapper — delegates to the canonical async implementation."""
|
|
442
|
+
return run_async_safely(
|
|
443
|
+
self._async_handle_call_tool(action, timeout_s=timeout_s)
|
|
444
|
+
)
|
|
436
445
|
|
|
437
|
-
|
|
438
|
-
|
|
446
|
+
async def _async_call_tool(self, tool_name: str, arguments: dict) -> Any:
|
|
447
|
+
"""
|
|
448
|
+
Async helper to call a tool on the MCP server.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
tool_name: Name of the tool to invoke.
|
|
452
|
+
arguments: Dictionary of arguments to pass to the tool.
|
|
439
453
|
|
|
440
|
-
|
|
454
|
+
Returns:
|
|
455
|
+
The result from the tool execution.
|
|
456
|
+
"""
|
|
457
|
+
async with self.mcp_session() as client:
|
|
458
|
+
return await client.call_tool(tool_name, arguments)
|
|
459
|
+
|
|
460
|
+
async def _async_handle_list_tools(self) -> ListToolsObservation:
|
|
461
|
+
"""Async version of _handle_list_tools — avoids run_async_safely."""
|
|
462
|
+
try:
|
|
463
|
+
current_mode = getattr(self, "_mode", None)
|
|
464
|
+
tools_result = await self._async_list_tools()
|
|
465
|
+
tools = []
|
|
441
466
|
for tool in tools_result:
|
|
442
467
|
if tool.name not in self._mode_tool_schemas:
|
|
443
468
|
tools.append(
|
|
@@ -449,11 +474,8 @@ class MCPEnvironment(Environment):
|
|
|
449
474
|
else {},
|
|
450
475
|
)
|
|
451
476
|
)
|
|
452
|
-
|
|
453
|
-
# Add mode-specific tools available in current mode
|
|
454
477
|
for tool_name, mode_schemas in self._mode_tool_schemas.items():
|
|
455
478
|
if None in mode_schemas:
|
|
456
|
-
# Tool available in all modes
|
|
457
479
|
schema = mode_schemas[None]
|
|
458
480
|
tools.append(
|
|
459
481
|
Tool(
|
|
@@ -463,7 +485,6 @@ class MCPEnvironment(Environment):
|
|
|
463
485
|
)
|
|
464
486
|
)
|
|
465
487
|
elif current_mode in mode_schemas:
|
|
466
|
-
# Tool available in current mode
|
|
467
488
|
schema = mode_schemas[current_mode]
|
|
468
489
|
tools.append(
|
|
469
490
|
Tool(
|
|
@@ -472,65 +493,30 @@ class MCPEnvironment(Environment):
|
|
|
472
493
|
input_schema=schema["input_schema"],
|
|
473
494
|
)
|
|
474
495
|
)
|
|
475
|
-
|
|
476
496
|
return ListToolsObservation(tools=tools)
|
|
477
|
-
|
|
478
497
|
except Exception as e:
|
|
479
|
-
# Return an observation with error in metadata
|
|
480
498
|
return ListToolsObservation(
|
|
481
499
|
tools=[],
|
|
482
|
-
metadata={
|
|
483
|
-
"error": str(e),
|
|
484
|
-
"error_type": "list_tools_failed",
|
|
485
|
-
},
|
|
500
|
+
metadata={"error": str(e), "error_type": "list_tools_failed"},
|
|
486
501
|
)
|
|
487
502
|
|
|
488
|
-
async def
|
|
489
|
-
"""
|
|
490
|
-
Async helper to list tools from the MCP client.
|
|
491
|
-
|
|
492
|
-
Returns:
|
|
493
|
-
List of tool objects from the MCP server.
|
|
494
|
-
"""
|
|
495
|
-
async with self.mcp_session() as client:
|
|
496
|
-
return await client.list_tools()
|
|
497
|
-
|
|
498
|
-
def _handle_call_tool(
|
|
503
|
+
async def _async_handle_call_tool(
|
|
499
504
|
self,
|
|
500
505
|
action: CallToolAction,
|
|
501
506
|
timeout_s: Optional[float] = None,
|
|
502
507
|
) -> CallToolObservation:
|
|
503
|
-
"""
|
|
504
|
-
Handle a CallToolAction by invoking the specified tool.
|
|
505
|
-
|
|
506
|
-
Args:
|
|
507
|
-
action: The CallToolAction containing tool_name and arguments.
|
|
508
|
-
timeout_s: Timeout in seconds. Defaults to MCP_TOOL_CALL_TIMEOUT (30s).
|
|
509
|
-
|
|
510
|
-
Returns:
|
|
511
|
-
CallToolObservation with the tool's result or an error.
|
|
512
|
-
"""
|
|
508
|
+
"""Async version of _handle_call_tool — avoids run_async_safely."""
|
|
513
509
|
timeout = timeout_s if timeout_s is not None else MCP_TOOL_CALL_TIMEOUT
|
|
514
|
-
|
|
515
|
-
# Check if this is a mode-specific tool
|
|
516
510
|
tool_name = action.tool_name
|
|
517
511
|
current_mode = getattr(self, "_mode", None)
|
|
518
512
|
|
|
519
513
|
if tool_name in self._mode_tools:
|
|
520
514
|
mode_info = self._mode_tools[tool_name]
|
|
521
|
-
|
|
522
|
-
# Check if tool is available in current mode
|
|
523
|
-
# Tool is available if:
|
|
524
|
-
# 1. It has a None mode (available in all modes), OR
|
|
525
|
-
# 2. It has an implementation for the current mode
|
|
526
515
|
if None in mode_info:
|
|
527
|
-
# Use the mode-agnostic version
|
|
528
516
|
func = mode_info[None]
|
|
529
517
|
elif current_mode in mode_info:
|
|
530
|
-
# Use the mode-specific version
|
|
531
518
|
func = mode_info[current_mode]
|
|
532
519
|
else:
|
|
533
|
-
# Tool not available in current mode
|
|
534
520
|
return CallToolObservation(
|
|
535
521
|
tool_name=tool_name,
|
|
536
522
|
result=None,
|
|
@@ -539,16 +525,11 @@ class MCPEnvironment(Environment):
|
|
|
539
525
|
message=f"Tool '{tool_name}' not available in {current_mode} mode",
|
|
540
526
|
),
|
|
541
527
|
)
|
|
542
|
-
|
|
543
|
-
# Call the mode-specific function directly
|
|
544
528
|
try:
|
|
545
|
-
# Check if function is async and await if necessary
|
|
546
529
|
if inspect.iscoroutinefunction(func):
|
|
547
|
-
result =
|
|
530
|
+
result = await func(**action.arguments)
|
|
548
531
|
else:
|
|
549
532
|
result = func(**action.arguments)
|
|
550
|
-
|
|
551
|
-
# Wrap result in CallToolResult format to match FastMCP behavior
|
|
552
533
|
return CallToolObservation(
|
|
553
534
|
tool_name=tool_name,
|
|
554
535
|
result=CallToolResult(
|
|
@@ -569,22 +550,12 @@ class MCPEnvironment(Environment):
|
|
|
569
550
|
),
|
|
570
551
|
)
|
|
571
552
|
|
|
572
|
-
# Not a mode-specific tool, use FastMCP
|
|
573
553
|
try:
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
asyncio.wait_for(
|
|
578
|
-
self._async_call_tool(action.tool_name, action.arguments),
|
|
579
|
-
timeout=timeout,
|
|
580
|
-
)
|
|
581
|
-
)
|
|
582
|
-
|
|
583
|
-
return CallToolObservation(
|
|
584
|
-
tool_name=action.tool_name,
|
|
585
|
-
result=result,
|
|
554
|
+
result = await asyncio.wait_for(
|
|
555
|
+
self._async_call_tool(action.tool_name, action.arguments),
|
|
556
|
+
timeout=timeout,
|
|
586
557
|
)
|
|
587
|
-
|
|
558
|
+
return CallToolObservation(tool_name=action.tool_name, result=result)
|
|
588
559
|
except asyncio.TimeoutError:
|
|
589
560
|
return CallToolObservation(
|
|
590
561
|
tool_name=action.tool_name,
|
|
@@ -594,11 +565,8 @@ class MCPEnvironment(Environment):
|
|
|
594
565
|
message=f"Tool '{action.tool_name}' timed out after {timeout} seconds",
|
|
595
566
|
),
|
|
596
567
|
)
|
|
597
|
-
|
|
598
568
|
except Exception as e:
|
|
599
569
|
error_message = str(e)
|
|
600
|
-
|
|
601
|
-
# Determine error type based on the exception
|
|
602
570
|
if (
|
|
603
571
|
"not found" in error_message.lower()
|
|
604
572
|
or "unknown tool" in error_message.lower()
|
|
@@ -611,29 +579,34 @@ class MCPEnvironment(Environment):
|
|
|
611
579
|
error_type = ToolErrorType.INVALID_ARGS
|
|
612
580
|
else:
|
|
613
581
|
error_type = ToolErrorType.EXECUTION_ERROR
|
|
614
|
-
|
|
615
582
|
return CallToolObservation(
|
|
616
583
|
tool_name=action.tool_name,
|
|
617
584
|
result=None,
|
|
618
|
-
error=ToolError(
|
|
619
|
-
error_type=error_type,
|
|
620
|
-
message=error_message,
|
|
621
|
-
),
|
|
585
|
+
error=ToolError(error_type=error_type, message=error_message),
|
|
622
586
|
)
|
|
623
587
|
|
|
624
|
-
async def
|
|
588
|
+
async def step_async(
|
|
589
|
+
self,
|
|
590
|
+
action: Action,
|
|
591
|
+
timeout_s: Optional[float] = None,
|
|
592
|
+
**kwargs: Any,
|
|
593
|
+
) -> Observation:
|
|
625
594
|
"""
|
|
626
|
-
Async
|
|
595
|
+
Async step that routes MCP actions without going through run_async_safely.
|
|
627
596
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
Returns:
|
|
633
|
-
The result from the tool execution.
|
|
597
|
+
The WebSocket handler calls this directly on the outer event loop, where
|
|
598
|
+
the MCP session is already open, avoiding the thread/event-loop deadlock
|
|
599
|
+
that occurs when the sync step() path is used via run_in_executor.
|
|
634
600
|
"""
|
|
635
|
-
|
|
636
|
-
return await
|
|
601
|
+
if isinstance(action, ListToolsAction):
|
|
602
|
+
return await self._async_handle_list_tools()
|
|
603
|
+
elif isinstance(action, CallToolAction):
|
|
604
|
+
return await self._async_handle_call_tool(action, timeout_s=timeout_s)
|
|
605
|
+
else:
|
|
606
|
+
loop = asyncio.get_event_loop()
|
|
607
|
+
return await loop.run_in_executor(
|
|
608
|
+
None, lambda: self._step_impl(action, timeout_s=timeout_s, **kwargs)
|
|
609
|
+
)
|
|
637
610
|
|
|
638
611
|
@abstractmethod
|
|
639
612
|
def _step_impl(
|
|
@@ -15,13 +15,15 @@ option (e.g. openenv push --enable-interface) or ENABLE_WEB_INTERFACE env var.
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import asyncio
|
|
18
|
+
import inspect
|
|
18
19
|
import json
|
|
19
20
|
from concurrent.futures import ThreadPoolExecutor
|
|
20
21
|
from datetime import datetime
|
|
21
22
|
from typing import Any, Callable, Dict, List, Optional, Type
|
|
22
23
|
|
|
23
24
|
import gradio as gr
|
|
24
|
-
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
25
|
+
from fastapi import Body, FastAPI, HTTPException, status, WebSocket, WebSocketDisconnect
|
|
26
|
+
from fastapi.responses import RedirectResponse
|
|
25
27
|
from pydantic import BaseModel, ConfigDict, Field
|
|
26
28
|
|
|
27
29
|
from .gradio_theme import OPENENV_GRADIO_CSS, OPENENV_GRADIO_THEME
|
|
@@ -269,6 +271,28 @@ class WebInterfaceManager:
|
|
|
269
271
|
# Thread pool for running sync code (e.g., Playwright sync API) in async context
|
|
270
272
|
self._executor = ThreadPoolExecutor(max_workers=1)
|
|
271
273
|
|
|
274
|
+
@staticmethod
|
|
275
|
+
def _get_valid_kwargs(
|
|
276
|
+
sig: inspect.Signature,
|
|
277
|
+
kwargs: Dict[str, Any],
|
|
278
|
+
skip_params: Optional[set[str]] = None,
|
|
279
|
+
) -> Dict[str, Any]:
|
|
280
|
+
"""Filter kwargs to only those accepted by the target function."""
|
|
281
|
+
skip_params = skip_params or set()
|
|
282
|
+
valid_kwargs: Dict[str, Any] = {}
|
|
283
|
+
has_var_kwargs = any(
|
|
284
|
+
param.kind == inspect.Parameter.VAR_KEYWORD
|
|
285
|
+
for param in sig.parameters.values()
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
for key, value in kwargs.items():
|
|
289
|
+
if key in skip_params:
|
|
290
|
+
continue
|
|
291
|
+
if key in sig.parameters or has_var_kwargs:
|
|
292
|
+
valid_kwargs[key] = value
|
|
293
|
+
|
|
294
|
+
return valid_kwargs
|
|
295
|
+
|
|
272
296
|
async def _run_sync_in_thread_pool(self, func, *args, **kwargs):
|
|
273
297
|
"""Run a synchronous function in the thread pool executor.
|
|
274
298
|
|
|
@@ -317,11 +341,24 @@ class WebInterfaceManager:
|
|
|
317
341
|
for client in disconnected_clients:
|
|
318
342
|
self.connected_clients.remove(client)
|
|
319
343
|
|
|
320
|
-
async def reset_environment(
|
|
344
|
+
async def reset_environment(
|
|
345
|
+
self, reset_kwargs: Optional[Dict[str, Any]] = None
|
|
346
|
+
) -> Dict[str, Any]:
|
|
321
347
|
"""Reset the environment and update state."""
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
348
|
+
reset_kwargs = reset_kwargs or {}
|
|
349
|
+
|
|
350
|
+
is_async = self.env.reset_async.__func__ is not Environment.reset_async
|
|
351
|
+
sig = inspect.signature(self.env.reset_async if is_async else self.env.reset)
|
|
352
|
+
valid_kwargs = self._get_valid_kwargs(sig, reset_kwargs)
|
|
353
|
+
|
|
354
|
+
if is_async:
|
|
355
|
+
observation = await self.env.reset_async(**valid_kwargs)
|
|
356
|
+
else:
|
|
357
|
+
# Run sync reset in thread pool to avoid blocking event loop
|
|
358
|
+
# and to support environments using sync libraries (e.g., Playwright)
|
|
359
|
+
observation = await self._run_sync_in_thread_pool(
|
|
360
|
+
self.env.reset, **valid_kwargs
|
|
361
|
+
)
|
|
325
362
|
state: State = self.env.state
|
|
326
363
|
|
|
327
364
|
# Serialize observation once using shared utility
|
|
@@ -428,6 +465,16 @@ def create_web_interface_app(
|
|
|
428
465
|
web_manager = WebInterfaceManager(env, action_cls, observation_cls, metadata)
|
|
429
466
|
|
|
430
467
|
# Web API routes first (so they take precedence over Gradio mount at /web)
|
|
468
|
+
@app.get("/", include_in_schema=False)
|
|
469
|
+
async def web_root():
|
|
470
|
+
"""Redirect the app root to the Gradio interface."""
|
|
471
|
+
return RedirectResponse(url="/web/")
|
|
472
|
+
|
|
473
|
+
@app.get("/web", include_in_schema=False)
|
|
474
|
+
async def web_root_no_slash():
|
|
475
|
+
"""Redirect /web to /web/ for mounted Gradio deployments behind proxies."""
|
|
476
|
+
return RedirectResponse(url="/web/")
|
|
477
|
+
|
|
431
478
|
@app.get("/web/metadata")
|
|
432
479
|
async def web_metadata():
|
|
433
480
|
"""Get environment metadata."""
|
|
@@ -449,9 +496,9 @@ def create_web_interface_app(
|
|
|
449
496
|
await web_manager.disconnect_websocket(websocket)
|
|
450
497
|
|
|
451
498
|
@app.post("/web/reset")
|
|
452
|
-
async def web_reset():
|
|
499
|
+
async def web_reset(request: Optional[Dict[str, Any]] = Body(default=None)):
|
|
453
500
|
"""Reset endpoint for web interface."""
|
|
454
|
-
return await web_manager.reset_environment()
|
|
501
|
+
return await web_manager.reset_environment(request)
|
|
455
502
|
|
|
456
503
|
@app.post("/web/step")
|
|
457
504
|
async def web_step(request: Dict[str, Any]):
|
|
@@ -475,7 +522,13 @@ def create_web_interface_app(
|
|
|
475
522
|
@app.get("/web/state")
|
|
476
523
|
async def web_state():
|
|
477
524
|
"""State endpoint for web interface."""
|
|
478
|
-
|
|
525
|
+
try:
|
|
526
|
+
return web_manager.get_state()
|
|
527
|
+
except RuntimeError as exc:
|
|
528
|
+
raise HTTPException(
|
|
529
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
530
|
+
detail=str(exc),
|
|
531
|
+
) from exc
|
|
479
532
|
|
|
480
533
|
action_fields = _extract_action_fields(action_cls)
|
|
481
534
|
is_chat_env = _is_chat_env(action_cls)
|
|
@@ -505,7 +558,7 @@ def create_web_interface_app(
|
|
|
505
558
|
)
|
|
506
559
|
gradio_blocks = gr.TabbedInterface(
|
|
507
560
|
[default_blocks, custom_blocks],
|
|
508
|
-
tab_names=["Playground", "
|
|
561
|
+
tab_names=["Playground", "Custom"],
|
|
509
562
|
title=get_gradio_display_title(metadata),
|
|
510
563
|
)
|
|
511
564
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|