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.
Files changed (82) hide show
  1. openenv-0.2.3/MANIFEST.in +2 -0
  2. {openenv-0.2.2/src/openenv.egg-info → openenv-0.2.3}/PKG-INFO +1 -1
  3. {openenv-0.2.2 → openenv-0.2.3}/pyproject.toml +5 -1
  4. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/__init__.py +12 -4
  5. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/pyproject.toml +1 -1
  6. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/interfaces.py +3 -1
  7. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/mcp_environment.py +64 -91
  8. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/web_interface.py +62 -9
  9. {openenv-0.2.2 → openenv-0.2.3/src/openenv.egg-info}/PKG-INFO +1 -1
  10. {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/SOURCES.txt +1 -0
  11. {openenv-0.2.2 → openenv-0.2.3}/LICENSE +0 -0
  12. {openenv-0.2.2 → openenv-0.2.3}/README.md +0 -0
  13. {openenv-0.2.2 → openenv-0.2.3}/setup.cfg +0 -0
  14. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/__init__.py +0 -0
  15. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/_discovery.py +0 -0
  16. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/auto_action.py +0 -0
  17. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/auto/auto_env.py +0 -0
  18. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/__init__.py +0 -0
  19. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/__main__.py +0 -0
  20. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/_cli_utils.py +0 -0
  21. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/_validation.py +0 -0
  22. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/__init__.py +0 -0
  23. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/build.py +0 -0
  24. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/fork.py +0 -0
  25. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/init.py +0 -0
  26. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/push.py +0 -0
  27. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/serve.py +0 -0
  28. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/skills.py +0 -0
  29. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/commands/validate.py +0 -0
  30. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/__init__.py +0 -0
  31. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/README.md +0 -0
  32. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/__init__.py +0 -0
  33. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/client.py +0 -0
  34. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/models.py +0 -0
  35. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/openenv.yaml +0 -0
  36. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/Dockerfile +0 -0
  37. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/__ENV_NAME___environment.py +0 -0
  38. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/__init__.py +0 -0
  39. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/app.py +0 -0
  40. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/cli/templates/openenv_env/server/requirements.txt +0 -0
  41. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/__init__.py +0 -0
  42. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/client_types.py +0 -0
  43. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/__init__.py +0 -0
  44. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/__init__.py +0 -0
  45. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/daytona_provider.py +0 -0
  46. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/providers.py +0 -0
  47. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/runtime/uv_provider.py +0 -0
  48. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/containers/test_local_docker_provider.py +0 -0
  49. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_client.py +0 -0
  50. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/__init__.py +0 -0
  51. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/base_transforms.py +0 -0
  52. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/exceptions.py +0 -0
  53. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/gradio_theme.py +0 -0
  54. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/gradio_ui.py +0 -0
  55. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/http_server.py +0 -0
  56. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/mcp_types.py +0 -0
  57. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/route_config.py +0 -0
  58. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/serialization.py +0 -0
  59. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/env_server/types.py +0 -0
  60. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/__init__.py +0 -0
  61. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/base.py +0 -0
  62. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/inspect_harness.py +0 -0
  63. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/evals/types.py +0 -0
  64. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/generic_client.py +0 -0
  65. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/llm_client.py +0 -0
  66. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/mcp_client.py +0 -0
  67. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/__init__.py +0 -0
  68. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/base.py +0 -0
  69. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/containers.py +0 -0
  70. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/llm_judge.py +0 -0
  71. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/rubrics/trajectory.py +0 -0
  72. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/sync_client.py +0 -0
  73. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/tools/__init__.py +0 -0
  74. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/tools/git_server_client.py +0 -0
  75. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/tools/local_python_executor.py +0 -0
  76. {openenv-0.2.2 → openenv-0.2.3}/src/openenv/core/utils.py +0 -0
  77. {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/dependency_links.txt +0 -0
  78. {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/entry_points.txt +0 -0
  79. {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/requires.txt +0 -0
  80. {openenv-0.2.2 → openenv-0.2.3}/src/openenv.egg-info/top_level.txt +0 -0
  81. {openenv-0.2.2 → openenv-0.2.3}/src/openenv_core/__init__.py +0 -0
  82. {openenv-0.2.2 → openenv-0.2.3}/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.2.3
4
4
  Summary: A unified framework for reinforcement learning environments
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -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.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
- 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"):
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.1",
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, TypedDict, TypeVar
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
- Handle a ListToolsAction by querying the MCP server.
428
+ Async helper to list tools from the MCP client.
425
429
 
426
430
  Returns:
427
- ListToolsObservation containing all available tools with their
428
- names, descriptions, and input schemas, filtered by current mode.
431
+ List of tool objects from the MCP server.
429
432
  """
430
- try:
431
- # Get current mode
432
- current_mode = getattr(self, "_mode", None)
433
+ async with self.mcp_session() as client:
434
+ return await client.list_tools()
433
435
 
434
- # Start with tools from FastMCP server (mode=None tools)
435
- tools_result = run_async_safely(self._async_list_tools())
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
- # Build list of Tool objects
438
- tools = []
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
- # Add FastMCP tools that are not mode-specific
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 _async_list_tools(self) -> list:
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 = run_async_safely(func(**action.arguments))
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
- # Run the async call_tool with timeout
575
- # Use run_async_safely to handle both sync and async contexts
576
- result = run_async_safely(
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 _async_call_tool(self, tool_name: str, arguments: dict) -> Any:
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 helper to call a tool on the MCP server.
595
+ Async step that routes MCP actions without going through run_async_safely.
627
596
 
628
- Args:
629
- tool_name: Name of the tool to invoke.
630
- arguments: Dictionary of arguments to pass to the tool.
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
- async with self.mcp_session() as client:
636
- return await client.call_tool(tool_name, arguments)
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(self) -> Dict[str, Any]:
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
- # Run sync reset in thread pool to avoid blocking event loop
323
- # and to support environments using sync libraries (e.g., Playwright)
324
- observation: Observation = await self._run_sync_in_thread_pool(self.env.reset)
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
- return web_manager.get_state()
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", "Visualization"],
561
+ tab_names=["Playground", "Custom"],
509
562
  title=get_gradio_display_title(metadata),
510
563
  )
511
564
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openenv
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A unified framework for reinforcement learning environments
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,4 +1,5 @@
1
1
  LICENSE
2
+ MANIFEST.in
2
3
  README.md
3
4
  pyproject.toml
4
5
  setup.cfg
File without changes
File without changes
File without changes