mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 18:33:15 +00:00
feat: add configurable sandbox template and fix sandbox lifecycle
This commit is contained in:
parent
db2a755463
commit
841aa536f5
1 changed files with 68 additions and 18 deletions
|
|
@ -28,14 +28,10 @@ import asyncio
|
|||
warnings.filterwarnings("ignore", message=".*Pydantic V1.*", category=UserWarning)
|
||||
|
||||
# Now safe to import agent (which imports LangChain modules)
|
||||
# Async wrapper for create_sandbox
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from deepagents import create_deep_agent
|
||||
from deepagents.backends.sandbox import SandboxBackendProtocol
|
||||
from deepagents_cli.agent import get_system_prompt
|
||||
from deepagents_cli.config import config, settings
|
||||
from deepagents_cli.integrations.sandbox_factory import create_sandbox
|
||||
from deepagents_cli.tools import fetch_url, http_request, web_search
|
||||
|
||||
# Local import for encryption
|
||||
|
|
@ -51,14 +47,45 @@ def _get_langsmith_api_key() -> str | None:
|
|||
return os.environ.get("LANGSMITH_API_KEY") or os.environ.get("LANGSMITH_API_KEY_PROD")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def create_sandbox_async(provider: str, **kwargs):
|
||||
"""Async wrapper around create_sandbox for compatibility."""
|
||||
# For langsmith provider, pass the API key explicitly
|
||||
if provider == "langsmith" and "api_key" not in kwargs:
|
||||
kwargs["api_key"] = _get_langsmith_api_key()
|
||||
with create_sandbox(provider, **kwargs) as sandbox:
|
||||
yield sandbox
|
||||
def _get_sandbox_template_config() -> tuple[str | None, str | None]:
|
||||
"""Get sandbox template configuration from environment.
|
||||
|
||||
Returns:
|
||||
Tuple of (template_name, template_image) from environment variables.
|
||||
Values are None if not set in environment.
|
||||
"""
|
||||
template_name = os.environ.get("DEFAULT_SANDBOX_TEMPLATE_NAME")
|
||||
template_image = os.environ.get("DEFAULT_SANDBOX_TEMPLATE_IMAGE")
|
||||
return template_name, template_image
|
||||
|
||||
|
||||
def _create_langsmith_sandbox(
|
||||
sandbox_id: str | None = None,
|
||||
) -> SandboxBackendProtocol:
|
||||
"""Create or connect to a LangSmith sandbox without automatic cleanup.
|
||||
|
||||
This function directly uses the LangSmithProvider to create/connect to sandboxes
|
||||
without the context manager cleanup, allowing sandboxes to persist across
|
||||
multiple agent invocations.
|
||||
|
||||
Args:
|
||||
sandbox_id: Optional existing sandbox ID to connect to.
|
||||
If None, creates a new sandbox.
|
||||
|
||||
Returns:
|
||||
SandboxBackendProtocol instance
|
||||
"""
|
||||
from deepagents_cli.integrations.langsmith import LangSmithProvider
|
||||
|
||||
api_key = _get_langsmith_api_key()
|
||||
template_name, template_image = _get_sandbox_template_config()
|
||||
|
||||
provider = LangSmithProvider(api_key=api_key)
|
||||
return provider.get_or_create(
|
||||
sandbox_id=sandbox_id,
|
||||
template=template_name,
|
||||
template_image=template_image,
|
||||
)
|
||||
|
||||
|
||||
def create_server_agent(
|
||||
|
|
@ -876,8 +903,8 @@ async def get_agent(config: RunnableConfig) -> Pregel: # noqa: PLR0915
|
|||
await client.threads.update(thread_id=thread_id, metadata={"sandbox_id": SANDBOX_CREATING})
|
||||
|
||||
try:
|
||||
sandbox_cm = create_sandbox_async("langsmith")
|
||||
sandbox_backend = await sandbox_cm.__aenter__()
|
||||
# Create sandbox without context manager cleanup (sandbox persists)
|
||||
sandbox_backend = await asyncio.to_thread(_create_langsmith_sandbox)
|
||||
logger.info("Sandbox created: %s", sandbox_backend.id)
|
||||
|
||||
# Update metadata immediately after sandbox creation so other callers
|
||||
|
|
@ -910,12 +937,35 @@ async def get_agent(config: RunnableConfig) -> Pregel: # noqa: PLR0915
|
|||
else:
|
||||
logger.info("Connecting to existing sandbox %s", sandbox_id)
|
||||
try:
|
||||
sandbox_cm = create_sandbox_async("langsmith", sandbox_id=sandbox_id)
|
||||
sandbox_backend = await sandbox_cm.__aenter__()
|
||||
# Connect to existing sandbox without context manager cleanup
|
||||
sandbox_backend = await asyncio.to_thread(
|
||||
_create_langsmith_sandbox, sandbox_id
|
||||
)
|
||||
logger.info("Connected to existing sandbox %s", sandbox_id)
|
||||
except Exception:
|
||||
logger.exception("Failed to connect to existing sandbox %s", sandbox_id)
|
||||
raise
|
||||
logger.warning(
|
||||
"Failed to connect to existing sandbox %s, creating new one", sandbox_id
|
||||
)
|
||||
# Reset sandbox_id and create a new sandbox
|
||||
await client.threads.update(
|
||||
thread_id=thread_id,
|
||||
metadata={"sandbox_id": SANDBOX_CREATING},
|
||||
)
|
||||
|
||||
try:
|
||||
sandbox_backend = await asyncio.to_thread(_create_langsmith_sandbox)
|
||||
logger.info("New sandbox created: %s", sandbox_backend.id)
|
||||
|
||||
await client.threads.update(
|
||||
thread_id=thread_id,
|
||||
metadata={"sandbox_id": sandbox_backend.id},
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to create replacement sandbox")
|
||||
await client.threads.update(
|
||||
thread_id=thread_id, metadata={"sandbox_id": None}
|
||||
)
|
||||
raise
|
||||
|
||||
thread = await client.threads.get(thread_id=thread_id)
|
||||
repo_dir = thread.get("metadata", {}).get("repo_dir")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue