mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Fix mypy errors in runtime/utils directory (#6902)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
0efe4feb2a
commit
86c6feafcc
@ -87,13 +87,14 @@ class RunloopRuntime(ActionExecutionClient):
|
||||
# Add some additional commands based on our image
|
||||
# NB: start off as root, action_execution_server will ultimately choose user but expects all context
|
||||
# (ie browser) to be installed as root
|
||||
start_command = (
|
||||
# Convert start_command list to a single command string with additional setup
|
||||
start_command_str = (
|
||||
'export MAMBA_ROOT_PREFIX=/openhands/micromamba && '
|
||||
'cd /openhands/code && '
|
||||
+ '/openhands/micromamba/bin/micromamba run -n openhands poetry config virtualenvs.path /openhands/poetry && '
|
||||
'/openhands/micromamba/bin/micromamba run -n openhands poetry config virtualenvs.path /openhands/poetry && '
|
||||
+ ' '.join(start_command)
|
||||
)
|
||||
entrypoint = f"sudo bash -c '{start_command}'"
|
||||
entrypoint = f"sudo bash -c '{start_command_str}'"
|
||||
|
||||
devbox = self.runloop_api_client.devboxes.create(
|
||||
entrypoint=entrypoint,
|
||||
|
||||
@ -4,8 +4,9 @@ import time
|
||||
import traceback
|
||||
import uuid
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
import bashlex
|
||||
import bashlex # type: ignore
|
||||
import libtmux
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@ -19,7 +20,7 @@ from openhands.events.observation.commands import (
|
||||
from openhands.utils.shutdown_listener import should_continue
|
||||
|
||||
|
||||
def split_bash_commands(commands):
|
||||
def split_bash_commands(commands: str) -> list[str]:
|
||||
if not commands.strip():
|
||||
return ['']
|
||||
try:
|
||||
@ -82,7 +83,7 @@ def escape_bash_special_chars(command: str) -> str:
|
||||
parts = []
|
||||
last_pos = 0
|
||||
|
||||
def visit_node(node):
|
||||
def visit_node(node: Any) -> None:
|
||||
nonlocal last_pos
|
||||
if (
|
||||
node.kind == 'redirect'
|
||||
@ -183,7 +184,7 @@ class BashSession:
|
||||
self._initialized = False
|
||||
self.max_memory_mb = max_memory_mb
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self.server = libtmux.Server()
|
||||
_shell_command = '/bin/bash'
|
||||
if self.username in ['root', 'openhands']:
|
||||
@ -203,7 +204,7 @@ class BashSession:
|
||||
session_name = f'openhands-{self.username}-{uuid.uuid4()}'
|
||||
self.session = self.server.new_session(
|
||||
session_name=session_name,
|
||||
start_directory=self.work_dir,
|
||||
start_directory=self.work_dir, # This parameter is supported by libtmux
|
||||
kill_session=True,
|
||||
x=1000,
|
||||
y=1000,
|
||||
@ -218,7 +219,7 @@ class BashSession:
|
||||
self.window = self.session.new_window(
|
||||
window_name='bash',
|
||||
window_shell=window_command,
|
||||
start_directory=self.work_dir,
|
||||
start_directory=self.work_dir, # This parameter is supported by libtmux
|
||||
)
|
||||
self.pane = self.window.attached_pane
|
||||
logger.debug(f'pane: {self.pane}; history_limit: {self.session.history_limit}')
|
||||
@ -241,7 +242,7 @@ class BashSession:
|
||||
self._cwd = os.path.abspath(self.work_dir)
|
||||
self._initialized = True
|
||||
|
||||
def __del__(self):
|
||||
def __del__(self) -> None:
|
||||
"""Ensure the session is closed when the object is destroyed."""
|
||||
self.close()
|
||||
|
||||
@ -256,7 +257,7 @@ class BashSession:
|
||||
)
|
||||
return content
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
"""Clean up the session."""
|
||||
if self._closed:
|
||||
return
|
||||
@ -264,7 +265,7 @@ class BashSession:
|
||||
self._closed = True
|
||||
|
||||
@property
|
||||
def cwd(self):
|
||||
def cwd(self) -> str:
|
||||
return self._cwd
|
||||
|
||||
def _is_special_key(self, command: str) -> bool:
|
||||
@ -273,7 +274,7 @@ class BashSession:
|
||||
_command = command.strip()
|
||||
return _command.startswith('C-') and len(_command) == 3
|
||||
|
||||
def _clear_screen(self):
|
||||
def _clear_screen(self) -> None:
|
||||
"""Clear the tmux pane screen and history."""
|
||||
self.pane.send_keys('C-l', enter=False)
|
||||
time.sleep(0.1)
|
||||
@ -424,7 +425,7 @@ class BashSession:
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
def _ready_for_next_command(self):
|
||||
def _ready_for_next_command(self) -> None:
|
||||
"""Reset the content buffer for a new command."""
|
||||
# Clear the current content
|
||||
self._clear_screen()
|
||||
@ -498,9 +499,9 @@ class BashSession:
|
||||
if len(splited_commands) > 1:
|
||||
return ErrorObservation(
|
||||
content=(
|
||||
f'ERROR: Cannot execute multiple commands at once.\n'
|
||||
f'Please run each command separately OR chain them into a single command via && or ;\n'
|
||||
f'Provided commands:\n{"\n".join(f"({i+1}) {cmd}" for i, cmd in enumerate(splited_commands))}'
|
||||
f"ERROR: Cannot execute multiple commands at once.\n"
|
||||
f"Please run each command separately OR chain them into a single command via && or ;\n"
|
||||
f"Provided commands:\n{'\n'.join(f'({i + 1}) {cmd}' for i, cmd in enumerate(splited_commands))}"
|
||||
)
|
||||
)
|
||||
|
||||
@ -573,8 +574,8 @@ class BashSession:
|
||||
logger.debug(
|
||||
f'PANE CONTENT GOT after {time.time() - _start_time:.2f} seconds'
|
||||
)
|
||||
logger.debug(f'BEGIN OF PANE CONTENT: {cur_pane_output.split("\n")[:10]}')
|
||||
logger.debug(f'END OF PANE CONTENT: {cur_pane_output.split("\n")[-10:]}')
|
||||
logger.debug(f"BEGIN OF PANE CONTENT: {cur_pane_output.split('\n')[:10]}")
|
||||
logger.debug(f"END OF PANE CONTENT: {cur_pane_output.split('\n')[-10:]}")
|
||||
ps1_matches = CmdOutputMetadata.matches_ps1_metadata(cur_pane_output)
|
||||
if cur_pane_output != last_pane_output:
|
||||
last_pane_output = cur_pane_output
|
||||
|
||||
@ -18,7 +18,7 @@ def get_action_execution_server_startup_command(
|
||||
python_prefix: list[str] = DEFAULT_PYTHON_PREFIX,
|
||||
override_user_id: int | None = None,
|
||||
override_username: str | None = None,
|
||||
):
|
||||
) -> list[str]:
|
||||
sandbox_config = app_config.sandbox
|
||||
|
||||
# Plugin args
|
||||
|
||||
@ -2,8 +2,9 @@ import os
|
||||
import re
|
||||
import tempfile
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
|
||||
from openhands_aci.utils.diff import get_diff
|
||||
from openhands_aci.utils.diff import get_diff # type: ignore
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@ -52,12 +53,12 @@ IMPORTANT:
|
||||
""".strip()
|
||||
|
||||
|
||||
def _extract_code(string):
|
||||
def _extract_code(string: str) -> str | None:
|
||||
pattern = r'```(?:\w*\n)?(.*?)```'
|
||||
matches = re.findall(pattern, string, re.DOTALL)
|
||||
if not matches:
|
||||
return None
|
||||
return matches[0]
|
||||
return str(matches[0])
|
||||
|
||||
|
||||
def get_new_file_contents(
|
||||
@ -102,7 +103,7 @@ class FileEditRuntimeMixin(FileEditRuntimeInterface):
|
||||
# This restricts the number of lines we can edit to avoid exceeding the token limit.
|
||||
MAX_LINES_TO_EDIT = 300
|
||||
|
||||
def __init__(self, enable_llm_editor: bool, *args, **kwargs):
|
||||
def __init__(self, enable_llm_editor: bool, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.enable_llm_editor = enable_llm_editor
|
||||
|
||||
@ -301,10 +302,10 @@ class FileEditRuntimeMixin(FileEditRuntimeInterface):
|
||||
'Here are some snippets that maybe relevant to the provided edit.\n'
|
||||
)
|
||||
for i, chunk in enumerate(topk_chunks):
|
||||
error_msg += f'[begin relevant snippet {i+1}. Line range: L{chunk.line_range[0]}-L{chunk.line_range[1]}. Similarity: {chunk.normalized_lcs}]\n'
|
||||
error_msg += f'[begin relevant snippet {i + 1}. Line range: L{chunk.line_range[0]}-L{chunk.line_range[1]}. Similarity: {chunk.normalized_lcs}]\n'
|
||||
error_msg += f'[Browse around it via `open_file("{action.path}", {(chunk.line_range[0] + chunk.line_range[1]) // 2})`]\n'
|
||||
error_msg += chunk.visualize() + '\n'
|
||||
error_msg += f'[end relevant snippet {i+1}]\n'
|
||||
error_msg += f'[end relevant snippet {i + 1}]\n'
|
||||
error_msg += '-' * 40 + '\n'
|
||||
|
||||
error_msg += 'Consider using `open_file` to explore around the relevant snippets if needed.\n'
|
||||
|
||||
@ -14,7 +14,7 @@ def resolve_path(
|
||||
working_directory: str,
|
||||
workspace_base: str,
|
||||
workspace_mount_path_in_sandbox: str,
|
||||
):
|
||||
) -> Path:
|
||||
"""Resolve a file path to a path on the host filesystem.
|
||||
|
||||
Args:
|
||||
@ -51,7 +51,7 @@ def resolve_path(
|
||||
return path_in_host_workspace
|
||||
|
||||
|
||||
def read_lines(all_lines: list[str], start=0, end=-1):
|
||||
def read_lines(all_lines: list[str], start: int = 0, end: int = -1) -> list[str]:
|
||||
start = max(start, 0)
|
||||
start = min(start, len(all_lines))
|
||||
end = -1 if end == -1 else max(end, 0)
|
||||
@ -69,7 +69,12 @@ def read_lines(all_lines: list[str], start=0, end=-1):
|
||||
|
||||
|
||||
async def read_file(
|
||||
path, workdir, workspace_base, workspace_mount_path_in_sandbox, start=0, end=-1
|
||||
path: str,
|
||||
workdir: str,
|
||||
workspace_base: str,
|
||||
workspace_mount_path_in_sandbox: str,
|
||||
start: int = 0,
|
||||
end: int = -1,
|
||||
) -> Observation:
|
||||
try:
|
||||
whole_path = resolve_path(
|
||||
@ -95,7 +100,7 @@ async def read_file(
|
||||
|
||||
def insert_lines(
|
||||
to_insert: list[str], original: list[str], start: int = 0, end: int = -1
|
||||
):
|
||||
) -> list[str]:
|
||||
"""Insert the new content to the original content based on start and end"""
|
||||
new_lines = [''] if start == 0 else original[:start]
|
||||
new_lines += [i + '\n' for i in to_insert]
|
||||
@ -104,13 +109,13 @@ def insert_lines(
|
||||
|
||||
|
||||
async def write_file(
|
||||
path,
|
||||
workdir,
|
||||
workspace_base,
|
||||
workspace_mount_path_in_sandbox,
|
||||
content,
|
||||
start=0,
|
||||
end=-1,
|
||||
path: str,
|
||||
workdir: str,
|
||||
workspace_base: str,
|
||||
workspace_mount_path_in_sandbox: str,
|
||||
content: str,
|
||||
start: int = 0,
|
||||
end: int = -1,
|
||||
) -> Observation:
|
||||
insert = content.split('\n')
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ class LogStreamer:
|
||||
self.stdout_thread.daemon = True
|
||||
self.stdout_thread.start()
|
||||
|
||||
def _stream_logs(self):
|
||||
def _stream_logs(self) -> None:
|
||||
"""Stream logs from the Docker container to stdout."""
|
||||
try:
|
||||
for log_line in self.log_generator:
|
||||
@ -37,11 +37,11 @@ class LogStreamer:
|
||||
except Exception as e:
|
||||
self.log('error', f'Error streaming docker logs to stdout: {e}')
|
||||
|
||||
def __del__(self):
|
||||
def __del__(self) -> None:
|
||||
if self.stdout_thread and self.stdout_thread.is_alive():
|
||||
self.close(timeout=5)
|
||||
|
||||
def close(self, timeout: float = 5.0):
|
||||
def close(self, timeout: float = 5.0) -> None:
|
||||
"""Clean shutdown of the log streaming."""
|
||||
self._stop_event.set()
|
||||
if self.stdout_thread and self.stdout_thread.is_alive():
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import threading
|
||||
|
||||
from memory_profiler import memory_usage
|
||||
from memory_profiler import memory_usage # type: ignore
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
@ -10,11 +10,11 @@ from openhands.core.logger import openhands_logger as logger
|
||||
class LogStream:
|
||||
"""Stream-like object that redirects writes to a logger."""
|
||||
|
||||
def write(self, message):
|
||||
def write(self, message: str) -> None:
|
||||
if message and not message.isspace():
|
||||
logger.info(f'[Memory usage] {message.strip()}')
|
||||
|
||||
def flush(self):
|
||||
def flush(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ class MemoryMonitor:
|
||||
self.log_stream = LogStream()
|
||||
self.enable = enable
|
||||
|
||||
def start_monitoring(self):
|
||||
def start_monitoring(self) -> None:
|
||||
"""Start monitoring memory usage."""
|
||||
if not self.enable:
|
||||
return
|
||||
@ -34,7 +34,7 @@ class MemoryMonitor:
|
||||
if self._monitoring_thread is not None:
|
||||
return
|
||||
|
||||
def monitor_process():
|
||||
def monitor_process() -> None:
|
||||
try:
|
||||
# Use memory_usage's built-in monitoring loop
|
||||
mem_usage = memory_usage(
|
||||
@ -55,7 +55,7 @@ class MemoryMonitor:
|
||||
self._monitoring_thread.start()
|
||||
logger.info('Memory monitoring started')
|
||||
|
||||
def stop_monitoring(self):
|
||||
def stop_monitoring(self) -> None:
|
||||
"""Stop monitoring memory usage."""
|
||||
if not self.enable:
|
||||
return
|
||||
|
||||
@ -11,7 +11,7 @@ from openhands.utils.tenacity_stop import stop_if_should_exit
|
||||
class RequestHTTPError(requests.HTTPError):
|
||||
"""Exception raised when an error occurs in a request with details."""
|
||||
|
||||
def __init__(self, *args, detail=None, **kwargs):
|
||||
def __init__(self, *args: Any, detail: Any = None, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.detail = detail
|
||||
|
||||
@ -22,7 +22,7 @@ class RequestHTTPError(requests.HTTPError):
|
||||
return s
|
||||
|
||||
|
||||
def is_retryable_error(exception):
|
||||
def is_retryable_error(exception: Any) -> bool:
|
||||
return (
|
||||
isinstance(exception, requests.HTTPError)
|
||||
and exception.response.status_code == 429
|
||||
@ -56,4 +56,4 @@ def send_request(
|
||||
response=e.response,
|
||||
detail=_json.get('detail') if _json is not None else None,
|
||||
) from e
|
||||
return response
|
||||
return response # type: ignore
|
||||
|
||||
@ -9,7 +9,7 @@ from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import docker
|
||||
from dirhash import dirhash
|
||||
from dirhash import dirhash # type: ignore
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
import openhands
|
||||
@ -25,7 +25,7 @@ class BuildFromImageType(Enum):
|
||||
LOCK = 'lock' # Fastest: Reuse the most recent image with the exact SAME dependencies (lock files)
|
||||
|
||||
|
||||
def get_runtime_image_repo():
|
||||
def get_runtime_image_repo() -> str:
|
||||
return os.getenv('OH_RUNTIME_RUNTIME_IMAGE_REPO', 'ghcr.io/all-hands-ai/runtime')
|
||||
|
||||
|
||||
@ -252,7 +252,7 @@ def prep_build_folder(
|
||||
base_image: str,
|
||||
build_from: BuildFromImageType,
|
||||
extra_deps: str | None,
|
||||
):
|
||||
) -> None:
|
||||
# Copy the source code to directory. It will end up in build_folder/code
|
||||
# If package is not found, build from source code
|
||||
openhands_source_dir = Path(openhands.__file__).parent
|
||||
@ -301,7 +301,7 @@ def truncate_hash(hash: str) -> str:
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
def get_hash_for_lock_files(base_image: str):
|
||||
def get_hash_for_lock_files(base_image: str) -> str:
|
||||
openhands_source_dir = Path(openhands.__file__).parent
|
||||
md5 = hashlib.md5()
|
||||
md5.update(base_image.encode())
|
||||
@ -318,11 +318,11 @@ def get_hash_for_lock_files(base_image: str):
|
||||
return result
|
||||
|
||||
|
||||
def get_tag_for_versioned_image(base_image: str):
|
||||
def get_tag_for_versioned_image(base_image: str) -> str:
|
||||
return base_image.replace('/', '_s_').replace(':', '_t_').lower()[-96:]
|
||||
|
||||
|
||||
def get_hash_for_source_files():
|
||||
def get_hash_for_source_files() -> str:
|
||||
openhands_source_dir = Path(openhands.__file__).parent
|
||||
dir_hash = dirhash(
|
||||
openhands_source_dir,
|
||||
@ -348,7 +348,7 @@ def _build_sandbox_image(
|
||||
versioned_tag: str | None,
|
||||
platform: str | None = None,
|
||||
extra_build_args: List[str] | None = None,
|
||||
):
|
||||
) -> str:
|
||||
"""Build and tag the sandbox image. The image will be tagged with all tags that do not yet exist."""
|
||||
names = [
|
||||
f'{runtime_image_repo}:{source_tag}',
|
||||
|
||||
@ -80,25 +80,29 @@ RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-memory-monitor && \
|
||||
{% macro install_dependencies() %}
|
||||
# Install all dependencies
|
||||
WORKDIR /openhands/code
|
||||
RUN \
|
||||
/openhands/micromamba/bin/micromamba config set changeps1 False && \
|
||||
# Configure Poetry and create virtual environment
|
||||
|
||||
# Configure micromamba and poetry
|
||||
RUN /openhands/micromamba/bin/micromamba config set changeps1 False && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry config virtualenvs.path /openhands/poetry && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry env use python3.12 && \
|
||||
# Install project dependencies
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry install --only main,runtime --no-interaction --no-root && \
|
||||
# Update and install additional tools
|
||||
apt-get update && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry env use python3.12
|
||||
|
||||
# Install project dependencies in smaller chunks
|
||||
RUN /openhands/micromamba/bin/micromamba run -n openhands poetry install --only main --no-interaction --no-root
|
||||
|
||||
RUN /openhands/micromamba/bin/micromamba run -n openhands poetry install --only runtime --no-interaction --no-root
|
||||
|
||||
# Install playwright and its dependencies
|
||||
RUN apt-get update && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry run pip install playwright && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \
|
||||
# Set environment variables
|
||||
echo "OH_INTERPRETER_PATH=$(/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print(sys.executable)")" >> /etc/environment && \
|
||||
# Clear caches
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . -n && \
|
||||
# Set permissions
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium
|
||||
|
||||
# Set environment variables and permissions
|
||||
RUN /openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print('OH_INTERPRETER_PATH=' + sys.executable)" >> /etc/environment && \
|
||||
chmod -R g+rws /openhands/poetry && \
|
||||
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \
|
||||
# Clean up
|
||||
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace
|
||||
|
||||
# Clear caches
|
||||
RUN /openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . -n && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||
/openhands/micromamba/bin/micromamba clean --all
|
||||
|
||||
|
||||
@ -15,7 +15,9 @@ def check_port_available(port: int) -> bool:
|
||||
sock.close()
|
||||
|
||||
|
||||
def find_available_tcp_port(min_port=30000, max_port=39999, max_attempts=10) -> int:
|
||||
def find_available_tcp_port(
|
||||
min_port: int = 30000, max_port: int = 39999, max_attempts: int = 10
|
||||
) -> int:
|
||||
"""Find an available TCP port in a specified range.
|
||||
|
||||
Args:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user