Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- __init__.py +18 -5
- models.py +6 -0
- opencode_openenv.egg-info/PKG-INFO +3 -3
- opencode_openenv.egg-info/SOURCES.txt +2 -1
- opencode_openenv.egg-info/requires.txt +2 -2
- pyproject.toml +2 -3
- server/opencode_environment.py +75 -10
- tests/conftest.py +45 -0
- uv.lock +5 -5
__init__.py
CHANGED
|
@@ -5,13 +5,14 @@ the OpenCode CLI agent against a caller-supplied LLM endpoint, runs the
|
|
| 5 |
caller-supplied verifier script, and returns reward + proxy trace +
|
| 6 |
workdir contents as a JSON-serialized :class:`RolloutResult`.
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
-
from
|
| 14 |
-
|
| 15 |
|
| 16 |
__all__ = [
|
| 17 |
"OpenCodeEnv",
|
|
@@ -19,3 +20,15 @@ __all__ = [
|
|
| 19 |
"RolloutResult",
|
| 20 |
"RolloutTurn",
|
| 21 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
caller-supplied verifier script, and returns reward + proxy trace +
|
| 6 |
workdir contents as a JSON-serialized :class:`RolloutResult`.
|
| 7 |
|
| 8 |
+
The directory name (``openenv``) matches the installed ``openenv-core``
|
| 9 |
+
namespace package, so we use lazy ``__getattr__`` resolution to avoid
|
| 10 |
+
eager imports that would collide with the installed package when this
|
| 11 |
+
folder is on ``sys.path`` (pytest rootdir, etc.).
|
| 12 |
"""
|
| 13 |
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
|
| 17 |
__all__ = [
|
| 18 |
"OpenCodeEnv",
|
|
|
|
| 20 |
"RolloutResult",
|
| 21 |
"RolloutTurn",
|
| 22 |
]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def __getattr__(name):
|
| 26 |
+
if name == "OpenCodeEnv":
|
| 27 |
+
from .client import OpenCodeEnv
|
| 28 |
+
|
| 29 |
+
return OpenCodeEnv
|
| 30 |
+
if name in ("OpenCodeState", "RolloutResult", "RolloutTurn"):
|
| 31 |
+
from . import models
|
| 32 |
+
|
| 33 |
+
return getattr(models, name)
|
| 34 |
+
raise AttributeError(f"module 'opencode_env_server' has no attribute {name!r}")
|
models.py
CHANGED
|
@@ -54,6 +54,12 @@ class RolloutResult(BaseModel):
|
|
| 54 |
# Errors (if any) surfacing from sandbox/proxy/verifier path
|
| 55 |
error: str | None = None
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
class OpenCodeState(State):
|
| 59 |
"""Persistent env state across calls to the single environment instance.
|
|
|
|
| 54 |
# Errors (if any) surfacing from sandbox/proxy/verifier path
|
| 55 |
error: str | None = None
|
| 56 |
|
| 57 |
+
# Diagnostic tails — populated when the primitive or verifier misbehaves so
|
| 58 |
+
# the client can see WHAT happened inside the sandbox without a second
|
| 59 |
+
# round-trip. Each is truncated to a few KB.
|
| 60 |
+
proxy_log_tail: str = ""
|
| 61 |
+
install_log_tail: str = ""
|
| 62 |
+
|
| 63 |
|
| 64 |
class OpenCodeState(State):
|
| 65 |
"""Persistent env state across calls to the single environment instance.
|
opencode_openenv.egg-info/PKG-INFO
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
Metadata-Version: 2.4
|
| 2 |
Name: opencode-openenv
|
| 3 |
Version: 0.1.0
|
| 4 |
-
Summary: OpenEnv OpenCode
|
| 5 |
Author-email: adithya-s-k <adithyaskolavi@gmail.com>
|
| 6 |
Requires-Python: >=3.10
|
| 7 |
-
Requires-Dist: openenv-core[core] @
|
| 8 |
-
Requires-Dist: openenv-opencode_env @
|
| 9 |
Requires-Dist: fastmcp>=3.0.0
|
| 10 |
Requires-Dist: fastapi>=0.115.0
|
| 11 |
Requires-Dist: uvicorn>=0.24.0
|
|
|
|
| 1 |
Metadata-Version: 2.4
|
| 2 |
Name: opencode-openenv
|
| 3 |
Version: 0.1.0
|
| 4 |
+
Summary: OpenEnv environment running the OpenCode coding agent inside an E2B sandbox, returning reward and per-turn trace.
|
| 5 |
Author-email: adithya-s-k <adithyaskolavi@gmail.com>
|
| 6 |
Requires-Python: >=3.10
|
| 7 |
+
Requires-Dist: openenv-core[core] @ file:///fsx/adithyaskolavi/projects/exp_rl/OpenEnv
|
| 8 |
+
Requires-Dist: openenv-opencode_env @ file:///fsx/adithyaskolavi/projects/exp_rl/OpenEnv/envs/opencode_env
|
| 9 |
Requires-Dist: fastmcp>=3.0.0
|
| 10 |
Requires-Dist: fastapi>=0.115.0
|
| 11 |
Requires-Dist: uvicorn>=0.24.0
|
opencode_openenv.egg-info/SOURCES.txt
CHANGED
|
@@ -18,4 +18,5 @@ server/__init__.py
|
|
| 18 |
server/app.py
|
| 19 |
server/gradio_ui.py
|
| 20 |
server/opencode_environment.py
|
| 21 |
-
server/requirements.txt
|
|
|
|
|
|
| 18 |
server/app.py
|
| 19 |
server/gradio_ui.py
|
| 20 |
server/opencode_environment.py
|
| 21 |
+
server/requirements.txt
|
| 22 |
+
tests/test_client.py
|
opencode_openenv.egg-info/requires.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
openenv-core[core] @
|
| 2 |
-
openenv-opencode_env @
|
| 3 |
fastmcp>=3.0.0
|
| 4 |
fastapi>=0.115.0
|
| 5 |
uvicorn>=0.24.0
|
|
|
|
| 1 |
+
openenv-core[core] @ file:///fsx/adithyaskolavi/projects/exp_rl/OpenEnv
|
| 2 |
+
openenv-opencode_env @ file:///fsx/adithyaskolavi/projects/exp_rl/OpenEnv/envs/opencode_env
|
| 3 |
fastmcp>=3.0.0
|
| 4 |
fastapi>=0.115.0
|
| 5 |
uvicorn>=0.24.0
|
pyproject.toml
CHANGED
|
@@ -11,9 +11,8 @@ authors = [
|
|
| 11 |
]
|
| 12 |
requires-python = ">=3.10"
|
| 13 |
dependencies = [
|
| 14 |
-
#
|
| 15 |
-
#
|
| 16 |
-
# on PR #471 and our opencode-harness branch stacked on top of it).
|
| 17 |
"openenv-core[core] @ git+https://github.com/adithya-s-k/OpenEnv.git@opencode-harness",
|
| 18 |
"openenv-opencode_env @ git+https://github.com/adithya-s-k/OpenEnv.git@opencode-harness#subdirectory=envs/opencode_env",
|
| 19 |
"fastmcp>=3.0.0",
|
|
|
|
| 11 |
]
|
| 12 |
requires-python = ">=3.10"
|
| 13 |
dependencies = [
|
| 14 |
+
# Pull both from the same branch so ``openenv.core.harness`` (only on
|
| 15 |
+
# PR #471's stack) is available alongside the primitive.
|
|
|
|
| 16 |
"openenv-core[core] @ git+https://github.com/adithya-s-k/OpenEnv.git@opencode-harness",
|
| 17 |
"openenv-opencode_env @ git+https://github.com/adithya-s-k/OpenEnv.git@opencode-harness#subdirectory=envs/opencode_env",
|
| 18 |
"fastmcp>=3.0.0",
|
server/opencode_environment.py
CHANGED
|
@@ -33,6 +33,14 @@ from openenv.core.env_server.types import Action, Observation
|
|
| 33 |
load_dotenv()
|
| 34 |
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
# Default test-script and reward paths inside the sandbox. The server writes
|
| 37 |
# the caller-supplied ``test_script`` text to this path; the verifier reads
|
| 38 |
# the reward file back out after it finishes.
|
|
@@ -180,6 +188,27 @@ class OpenCodeEnvironment(MCPEnvironment):
|
|
| 180 |
},
|
| 181 |
)
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
@property
|
| 184 |
def state(self) -> Any:
|
| 185 |
return self._state
|
|
@@ -270,8 +299,20 @@ class OpenCodeEnvironment(MCPEnvironment):
|
|
| 270 |
}
|
| 271 |
for raw in summary.proxy_turns:
|
| 272 |
result.proxy_turns.append(self._turn_cls(**_clamp_turn(raw)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
except Exception as exc:
|
| 274 |
result.error = f"{type(exc).__name__}: {exc}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
finally:
|
| 276 |
if session is not None:
|
| 277 |
try:
|
|
@@ -294,12 +335,17 @@ class OpenCodeEnvironment(MCPEnvironment):
|
|
| 294 |
|
| 295 |
|
| 296 |
def _qualify_model(provider: str, model: str) -> str:
|
| 297 |
-
"""Return a ``<provider>/<model>`` string
|
| 298 |
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
| 301 |
"""
|
| 302 |
-
|
|
|
|
|
|
|
| 303 |
return model
|
| 304 |
return f"{provider}/{model}"
|
| 305 |
|
|
@@ -320,13 +366,19 @@ def _read_reward(sandbox: Any, reward_path: str) -> Optional[float]:
|
|
| 320 |
def _clamp_turn(turn: dict[str, Any]) -> dict[str, Any]:
|
| 321 |
"""Clamp per-turn payload sizes to keep responses under a reasonable cap."""
|
| 322 |
out = dict(turn)
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
),
|
| 328 |
-
"usage":
|
| 329 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
req = out.get("request") or {}
|
| 331 |
messages = req.get("messages") or []
|
| 332 |
# Keep request messages (trainer needs them) but drop very long tool schemas.
|
|
@@ -350,3 +402,16 @@ def _tail(events: list[dict[str, Any]], n: int) -> str:
|
|
| 350 |
if not events:
|
| 351 |
return ""
|
| 352 |
return "\n".join(json.dumps(e) for e in events[-n:])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
load_dotenv()
|
| 34 |
|
| 35 |
|
| 36 |
+
# One rollout (sandbox create + opencode install + opencode run + verifier)
|
| 37 |
+
# typically takes 30-180s and can spike to 600s under load. Override
|
| 38 |
+
# OpenEnv's 30s MCP tool-call default so the server doesn't cut our tool
|
| 39 |
+
# off mid-flight. This default still defers to an explicit ``timeout_s``
|
| 40 |
+
# on the StepRequest when the client specifies one.
|
| 41 |
+
_RUN_ROLLOUT_TIMEOUT_S = 900.0
|
| 42 |
+
|
| 43 |
+
|
| 44 |
# Default test-script and reward paths inside the sandbox. The server writes
|
| 45 |
# the caller-supplied ``test_script`` text to this path; the verifier reads
|
| 46 |
# the reward file back out after it finishes.
|
|
|
|
| 188 |
},
|
| 189 |
)
|
| 190 |
|
| 191 |
+
def step(
|
| 192 |
+
self,
|
| 193 |
+
action: Action,
|
| 194 |
+
timeout_s: Optional[float] = None,
|
| 195 |
+
**kwargs: Any,
|
| 196 |
+
) -> Observation:
|
| 197 |
+
"""Override the MCP 30s default with a rollout-friendly budget."""
|
| 198 |
+
if timeout_s is None:
|
| 199 |
+
timeout_s = _RUN_ROLLOUT_TIMEOUT_S
|
| 200 |
+
return super().step(action, timeout_s=timeout_s, **kwargs)
|
| 201 |
+
|
| 202 |
+
async def step_async(
|
| 203 |
+
self,
|
| 204 |
+
action: Action,
|
| 205 |
+
timeout_s: Optional[float] = None,
|
| 206 |
+
**kwargs: Any,
|
| 207 |
+
) -> Observation:
|
| 208 |
+
if timeout_s is None:
|
| 209 |
+
timeout_s = _RUN_ROLLOUT_TIMEOUT_S
|
| 210 |
+
return await super().step_async(action, timeout_s=timeout_s, **kwargs)
|
| 211 |
+
|
| 212 |
@property
|
| 213 |
def state(self) -> Any:
|
| 214 |
return self._state
|
|
|
|
| 299 |
}
|
| 300 |
for raw in summary.proxy_turns:
|
| 301 |
result.proxy_turns.append(self._turn_cls(**_clamp_turn(raw)))
|
| 302 |
+
|
| 303 |
+
# Diagnostic capture — always try to grab the in-sandbox proxy log.
|
| 304 |
+
# Useful when productive_turns=0 or opencode exits abnormally.
|
| 305 |
+
result.proxy_log_tail = _read_safe(
|
| 306 |
+
session.sandbox, "/home/user/logs/agent/proxy.log", tail_chars=4000
|
| 307 |
+
)
|
| 308 |
except Exception as exc:
|
| 309 |
result.error = f"{type(exc).__name__}: {exc}"
|
| 310 |
+
# Even on failure, try to pull the proxy log if the sandbox still
|
| 311 |
+
# exists — it often contains the actual upstream HTTP error.
|
| 312 |
+
if session is not None:
|
| 313 |
+
result.proxy_log_tail = _read_safe(
|
| 314 |
+
session.sandbox, "/home/user/logs/agent/proxy.log", tail_chars=4000
|
| 315 |
+
)
|
| 316 |
finally:
|
| 317 |
if session is not None:
|
| 318 |
try:
|
|
|
|
| 335 |
|
| 336 |
|
| 337 |
def _qualify_model(provider: str, model: str) -> str:
|
| 338 |
+
"""Return a ``<provider>/<model>`` string the primitive can split cleanly.
|
| 339 |
|
| 340 |
+
The primitive splits ``config.model`` on the first ``/`` to recover the
|
| 341 |
+
upstream model id. If the caller passes a model that already contains a
|
| 342 |
+
slash (e.g. ``Qwen/Qwen3.5-4B``), we still prepend the provider so the
|
| 343 |
+
split separates provider from model and the model part round-trips
|
| 344 |
+
intact (``openai_compatible/Qwen/Qwen3.5-4B`` → upstream ``Qwen/Qwen3.5-4B``).
|
| 345 |
"""
|
| 346 |
+
# Strip an existing <provider>/ prefix only if it matches the configured
|
| 347 |
+
# provider verbatim — otherwise treat the whole string as the model id.
|
| 348 |
+
if model.startswith(provider + "/"):
|
| 349 |
return model
|
| 350 |
return f"{provider}/{model}"
|
| 351 |
|
|
|
|
| 366 |
def _clamp_turn(turn: dict[str, Any]) -> dict[str, Any]:
|
| 367 |
"""Clamp per-turn payload sizes to keep responses under a reasonable cap."""
|
| 368 |
out = dict(turn)
|
| 369 |
+
raw_response = out.get("response") or {}
|
| 370 |
+
choices = raw_response.get("choices") or []
|
| 371 |
+
first_choice = choices[0] if choices else {}
|
| 372 |
+
compact: dict[str, Any] = {
|
| 373 |
+
"finish_reason": first_choice.get("finish_reason"),
|
| 374 |
+
"usage": raw_response.get("usage"),
|
| 375 |
}
|
| 376 |
+
# Surface upstream errors captured by the proxy so they reach the client.
|
| 377 |
+
if raw_response.get("upstream_error") is not None:
|
| 378 |
+
compact["upstream_error"] = raw_response["upstream_error"]
|
| 379 |
+
if raw_response.get("upstream_status") is not None:
|
| 380 |
+
compact["upstream_status"] = raw_response["upstream_status"]
|
| 381 |
+
out["response"] = compact
|
| 382 |
req = out.get("request") or {}
|
| 383 |
messages = req.get("messages") or []
|
| 384 |
# Keep request messages (trainer needs them) but drop very long tool schemas.
|
|
|
|
| 402 |
if not events:
|
| 403 |
return ""
|
| 404 |
return "\n".join(json.dumps(e) for e in events[-n:])
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def _read_safe(sandbox: Any, path: str, *, tail_chars: int = 4000) -> str:
|
| 408 |
+
"""Read a file from the sandbox, returning its last ``tail_chars`` chars.
|
| 409 |
+
|
| 410 |
+
Returns empty string if the file is missing or unreadable. Used for
|
| 411 |
+
diagnostic tails so we never mask a real failure with a read error.
|
| 412 |
+
"""
|
| 413 |
+
try:
|
| 414 |
+
content = sandbox.read_text(path) or ""
|
| 415 |
+
except Exception:
|
| 416 |
+
return ""
|
| 417 |
+
return content[-tail_chars:]
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Conftest for opencode-openenv tests.
|
| 2 |
+
|
| 3 |
+
The env directory is named ``openenv`` which collides with the installed
|
| 4 |
+
``openenv-core`` namespace when pytest adds the parent directory
|
| 5 |
+
(``…/opencode``) to ``sys.path``. Strip the shadowing entries and evict
|
| 6 |
+
any already-loaded stale modules so ``import openenv.core`` resolves to
|
| 7 |
+
the installed package.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
_OPENENV_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 17 |
+
_OPENCODE_PARENT = os.path.dirname(_OPENENV_DIR)
|
| 18 |
+
|
| 19 |
+
for _entry in (_OPENCODE_PARENT, _OPENENV_DIR):
|
| 20 |
+
while _entry in sys.path:
|
| 21 |
+
sys.path.remove(_entry)
|
| 22 |
+
|
| 23 |
+
_stale_candidates = [
|
| 24 |
+
k
|
| 25 |
+
for k in list(sys.modules)
|
| 26 |
+
if (k == "openenv" or k.startswith("openenv."))
|
| 27 |
+
and not k.startswith("openenv.tests") # keep the conftest's own module
|
| 28 |
+
]
|
| 29 |
+
for _stale in _stale_candidates:
|
| 30 |
+
module = sys.modules[_stale]
|
| 31 |
+
module_file = getattr(module, "__file__", None) or ""
|
| 32 |
+
# Only evict the modules that resolved to our shadow env directory.
|
| 33 |
+
if module_file.startswith(_OPENENV_DIR + os.sep) or (
|
| 34 |
+
_OPENENV_DIR in module_file and "/.venv/" not in module_file
|
| 35 |
+
):
|
| 36 |
+
sys.modules.pop(_stale, None)
|
| 37 |
+
|
| 38 |
+
# Sanity check — fail loudly at collection time if the import still breaks.
|
| 39 |
+
try:
|
| 40 |
+
import openenv.core # noqa: F401
|
| 41 |
+
except ImportError as exc:
|
| 42 |
+
raise ImportError(
|
| 43 |
+
"openenv.core still not importable after sys.path scrub; check that "
|
| 44 |
+
"openenv-core is installed in the env's .venv."
|
| 45 |
+
) from exc
|
uv.lock
CHANGED
|
@@ -223,11 +223,11 @@ wheels = [
|
|
| 223 |
|
| 224 |
[[package]]
|
| 225 |
name = "cachetools"
|
| 226 |
-
version = "7.0.
|
| 227 |
source = { registry = "https://pypi.org/simple" }
|
| 228 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 229 |
wheels = [
|
| 230 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 231 |
]
|
| 232 |
|
| 233 |
[[package]]
|
|
@@ -1710,7 +1710,7 @@ dev = [
|
|
| 1710 |
[[package]]
|
| 1711 |
name = "openenv-core"
|
| 1712 |
version = "0.2.3"
|
| 1713 |
-
source = { git = "https://github.com/adithya-s-k/OpenEnv.git?rev=opencode-harness#
|
| 1714 |
dependencies = [
|
| 1715 |
{ name = "fastapi" },
|
| 1716 |
{ name = "fastmcp" },
|
|
@@ -1741,7 +1741,7 @@ core = [
|
|
| 1741 |
[[package]]
|
| 1742 |
name = "openenv-opencode-env"
|
| 1743 |
version = "0.1.0"
|
| 1744 |
-
source = { git = "https://github.com/adithya-s-k/OpenEnv.git?subdirectory=envs%2Fopencode_env&rev=opencode-harness#
|
| 1745 |
dependencies = [
|
| 1746 |
{ name = "e2b" },
|
| 1747 |
{ name = "fastapi" },
|
|
|
|
| 223 |
|
| 224 |
[[package]]
|
| 225 |
name = "cachetools"
|
| 226 |
+
version = "7.0.6"
|
| 227 |
source = { registry = "https://pypi.org/simple" }
|
| 228 |
+
sdist = { url = "https://files.pythonhosted.org/packages/76/7b/1755ed2c6bfabd1d98b37ae73152f8dcf94aa40fee119d163c19ed484704/cachetools-7.0.6.tar.gz", hash = "sha256:e5d524d36d65703a87243a26ff08ad84f73352adbeafb1cde81e207b456aaf24", size = 37526, upload-time = "2026-04-20T19:02:23.289Z" }
|
| 229 |
wheels = [
|
| 230 |
+
{ url = "https://files.pythonhosted.org/packages/fe/c4/cf76242a5da1410917107ff14551764aa405a5fd10cd10cf9a5ca8fa77f4/cachetools-7.0.6-py3-none-any.whl", hash = "sha256:4e94956cfdd3086f12042cdd29318f5ced3893014f7d0d059bf3ead3f85b7f8b", size = 13976, upload-time = "2026-04-20T19:02:21.187Z" },
|
| 231 |
]
|
| 232 |
|
| 233 |
[[package]]
|
|
|
|
| 1710 |
[[package]]
|
| 1711 |
name = "openenv-core"
|
| 1712 |
version = "0.2.3"
|
| 1713 |
+
source = { git = "https://github.com/adithya-s-k/OpenEnv.git?rev=opencode-harness#4f8bc23dc0e860ac990d8fd0ebdac7a5498e56a3" }
|
| 1714 |
dependencies = [
|
| 1715 |
{ name = "fastapi" },
|
| 1716 |
{ name = "fastmcp" },
|
|
|
|
| 1741 |
[[package]]
|
| 1742 |
name = "openenv-opencode-env"
|
| 1743 |
version = "0.1.0"
|
| 1744 |
+
source = { git = "https://github.com/adithya-s-k/OpenEnv.git?subdirectory=envs%2Fopencode_env&rev=opencode-harness#4f8bc23dc0e860ac990d8fd0ebdac7a5498e56a3" }
|
| 1745 |
dependencies = [
|
| 1746 |
{ name = "e2b" },
|
| 1747 |
{ name = "fastapi" },
|