Files
deepagent/deepagents_sourcecode/libs/deepagents-cli/deepagents_cli/integrations/runloop.py
HyunjunJeon af5fbfabec 문서 추가: Context Engineering 문서 추가 및 deepagents_sourcecode 한국어 번역
- Context_Engineering.md: 에이전트 컨텍스트 엔지니어링 개념 정리 문서 추가
- Context_Engineering_Research.ipynb: 연구 노트북 업데이트
- deepagents_sourcecode/: docstring과 주석을 한국어로 번역
2026-01-11 17:55:52 +09:00

127 lines
4.5 KiB
Python

"""Runloop용 `BackendProtocol` 구현입니다.
BackendProtocol implementation for Runloop.
"""
try:
from runloop_api_client import Runloop
except ImportError:
msg = (
"runloop_api_client package is required for RunloopBackend. "
"Install with `pip install runloop_api_client`."
)
raise ImportError(msg) from None
import os
from deepagents.backends.protocol import ExecuteResponse, FileDownloadResponse, FileUploadResponse
from deepagents.backends.sandbox import BaseSandbox
class RunloopBackend(BaseSandbox):
"""Backend that operates on files in a Runloop devbox.
This implementation uses the Runloop API client to execute commands
and manipulate files within a remote devbox environment.
"""
def __init__(
self,
devbox_id: str,
client: Runloop | None = None,
api_key: str | None = None,
) -> None:
"""Initialize Runloop protocol.
Args:
devbox_id: ID of the Runloop devbox to operate on.
client: Optional existing Runloop client instance
api_key: Optional API key for creating a new client
(defaults to RUNLOOP_API_KEY environment variable)
"""
if client and api_key:
msg = "Provide either client or bearer_token, not both."
raise ValueError(msg)
if client is None:
api_key = api_key or os.environ.get("RUNLOOP_API_KEY", None)
if api_key is None:
msg = "Either client or bearer_token must be provided."
raise ValueError(msg)
client = Runloop(bearer_token=api_key)
self._client = client
self._devbox_id = devbox_id
self._timeout = 30 * 60
@property
def id(self) -> str:
"""Unique identifier for the sandbox backend."""
return self._devbox_id
def execute(
self,
command: str,
) -> ExecuteResponse:
"""Execute a command in the devbox and return ExecuteResponse.
Args:
command: Full shell command string to execute.
timeout: Maximum execution time in seconds (default: 30 minutes).
Returns:
ExecuteResponse with combined output, exit code, optional signal, and truncation flag.
"""
result = self._client.devboxes.execute_and_await_completion(
devbox_id=self._devbox_id,
command=command,
timeout=self._timeout,
)
# Combine stdout and stderr
output = result.stdout or ""
if result.stderr:
output += "\n" + result.stderr if output else result.stderr
return ExecuteResponse(
output=output,
exit_code=result.exit_status,
truncated=False, # Runloop doesn't provide truncation info
)
def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
"""Download multiple files from the Runloop devbox.
Downloads files individually using the Runloop API. Returns a list of
FileDownloadResponse objects preserving order and reporting per-file
errors rather than raising exceptions.
TODO: Implement proper error handling with standardized FileOperationError codes.
Currently only implements happy path.
"""
responses: list[FileDownloadResponse] = []
for path in paths:
# devboxes.download_file returns a BinaryAPIResponse which exposes .read()
resp = self._client.devboxes.download_file(self._devbox_id, path=path)
content = resp.read()
responses.append(FileDownloadResponse(path=path, content=content, error=None))
return responses
def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
"""Upload multiple files to the Runloop devbox.
Uploads files individually using the Runloop API. Returns a list of
FileUploadResponse objects preserving order and reporting per-file
errors rather than raising exceptions.
TODO: Implement proper error handling with standardized FileOperationError codes.
Currently only implements happy path.
"""
responses: list[FileUploadResponse] = []
for path, content in files:
# The Runloop client expects 'file' as bytes or a file-like object
self._client.devboxes.upload_file(self._devbox_id, path=path, file=content)
responses.append(FileUploadResponse(path=path, error=None))
return responses