diff --git a/openhands/agenthub/codeact_agent/codeact_agent.py b/openhands/agenthub/codeact_agent/codeact_agent.py index 30bc9d2991..fa60e32340 100644 --- a/openhands/agenthub/codeact_agent/codeact_agent.py +++ b/openhands/agenthub/codeact_agent/codeact_agent.py @@ -266,5 +266,6 @@ class CodeActAgent(Agent): def response_to_actions(self, response: 'ModelResponse') -> list['Action']: return codeact_function_calling.response_to_actions( - response, mcp_tool_names=list(self.mcp_tools.keys()), + response, + mcp_tool_names=list(self.mcp_tools.keys()), ) diff --git a/openhands/agenthub/loc_agent/function_calling.py b/openhands/agenthub/loc_agent/function_calling.py index 2f68c2ba7f..32fac63cb6 100644 --- a/openhands/agenthub/loc_agent/function_calling.py +++ b/openhands/agenthub/loc_agent/function_calling.py @@ -5,14 +5,13 @@ This is similar to the functionality of `CodeActResponseParser`. import json - from litellm import ( ChatCompletionToolParam, ModelResponse, ) -from openhands.agenthub.codeact_agent.tools import FinishTool from openhands.agenthub.codeact_agent.function_calling import combine_thought +from openhands.agenthub.codeact_agent.tools import FinishTool from openhands.agenthub.loc_agent.tools import ( SearchEntityTool, SearchRepoTool, @@ -32,7 +31,8 @@ from openhands.events.tool import ToolCallMetadata def response_to_actions( - response: ModelResponse, mcp_tool_names: list[str] | None = None, + response: ModelResponse, + mcp_tool_names: list[str] | None = None, ) -> list[Action]: actions: list[Action] = [] assert len(response.choices) == 1, 'Only one choice is supported for now' @@ -87,7 +87,7 @@ def response_to_actions( raise FunctionCallNotExistsError( f'Tool {tool_call.function.name} is not registered. (arguments: {arguments}). Please check the tool name and retry with an existing tool.' ) - + # We only add thought to the first action if i == 0: action = combine_thought(action, thought) @@ -106,7 +106,7 @@ def response_to_actions( wait_for_response=True, ) ) - + # Add response id to actions # This will ensure we can match both actions without tool calls (e.g. MessageAction) # and actions with tool calls (e.g. CmdRunAction, IPythonRunCellAction, etc.) @@ -116,7 +116,7 @@ def response_to_actions( assert len(actions) >= 1 return actions - + def get_tools() -> list[ChatCompletionToolParam]: tools = [FinishTool] diff --git a/openhands/agenthub/loc_agent/loc_agent.py b/openhands/agenthub/loc_agent/loc_agent.py index 7e6c574de6..9fbc4c6150 100644 --- a/openhands/agenthub/loc_agent/loc_agent.py +++ b/openhands/agenthub/loc_agent/loc_agent.py @@ -1,13 +1,12 @@ -from openhands.agenthub.codeact_agent import CodeActAgent +from typing import TYPE_CHECKING + import openhands.agenthub.loc_agent.function_calling as locagent_function_calling +from openhands.agenthub.codeact_agent import CodeActAgent from openhands.core.config import AgentConfig from openhands.core.logger import openhands_logger as logger from openhands.llm.llm import LLM -from typing import TYPE_CHECKING - if TYPE_CHECKING: - from openhands.events.action import Action from openhands.llm.llm import ModelResponse @@ -35,5 +34,6 @@ class LocAgent(CodeActAgent): def response_to_actions(self, response: 'ModelResponse') -> list['Action']: return locagent_function_calling.response_to_actions( - response, mcp_tool_names=list(self.mcp_tools.keys()), + response, + mcp_tool_names=list(self.mcp_tools.keys()), ) diff --git a/openhands/events/observation/mcp.py b/openhands/events/observation/mcp.py index ca4aa5623e..019e1c72e8 100644 --- a/openhands/events/observation/mcp.py +++ b/openhands/events/observation/mcp.py @@ -11,7 +11,9 @@ class MCPObservation(Observation): observation: str = ObservationType.MCP name: str = '' # The name of the MCP tool that was called - arguments: dict[str, Any] = field(default_factory=dict) # The arguments passed to the MCP tool + arguments: dict[str, Any] = field( + default_factory=dict + ) # The arguments passed to the MCP tool @property def message(self) -> str: diff --git a/openhands/integrations/templates/resolver/github/issue_comment_prompt.j2 b/openhands/integrations/templates/resolver/github/issue_comment_prompt.j2 index 352927cfd7..a84b0bdbc7 100644 --- a/openhands/integrations/templates/resolver/github/issue_comment_prompt.j2 +++ b/openhands/integrations/templates/resolver/github/issue_comment_prompt.j2 @@ -1 +1 @@ -{{ issue_comment }} \ No newline at end of file +{{ issue_comment }} diff --git a/openhands/integrations/templates/resolver/github/issue_labeled_prompt.j2 b/openhands/integrations/templates/resolver/github/issue_labeled_prompt.j2 index eece89fec2..358ed79a74 100644 --- a/openhands/integrations/templates/resolver/github/issue_labeled_prompt.j2 +++ b/openhands/integrations/templates/resolver/github/issue_labeled_prompt.j2 @@ -1 +1 @@ -Please fix issue number #{{ issue_number }} in your repository. \ No newline at end of file +Please fix issue number #{{ issue_number }} in your repository. diff --git a/openhands/integrations/templates/resolver/github/pr_update_prompt.j2 b/openhands/integrations/templates/resolver/github/pr_update_prompt.j2 index ff1ef88774..987ac3ac59 100644 --- a/openhands/integrations/templates/resolver/github/pr_update_prompt.j2 +++ b/openhands/integrations/templates/resolver/github/pr_update_prompt.j2 @@ -1 +1 @@ -{{ pr_comment }} \ No newline at end of file +{{ pr_comment }} diff --git a/openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja b/openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja index 9d240983db..02e20f2da2 100644 --- a/openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja +++ b/openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja @@ -4,4 +4,4 @@ You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instructi Some basic information about this repository: {{ repo_instruction }}{% endif %} -When you think you have fixed the issue through code changes, please finish the interaction. \ No newline at end of file +When you think you have fixed the issue through code changes, please finish the interaction. diff --git a/openhands/resolver/prompts/resolve/basic-followup-conversation-instructions.jinja b/openhands/resolver/prompts/resolve/basic-followup-conversation-instructions.jinja index eceef63f82..8f15a01545 100644 --- a/openhands/resolver/prompts/resolve/basic-followup-conversation-instructions.jinja +++ b/openhands/resolver/prompts/resolve/basic-followup-conversation-instructions.jinja @@ -13,4 +13,4 @@ You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instructi Some basic information about this repository: {{ repo_instruction }}{% endif %} -When you think you have fixed the issue through code changes, please finish the interaction. \ No newline at end of file +When you think you have fixed the issue through code changes, please finish the interaction. diff --git a/openhands/resolver/prompts/resolve/basic-with-tests.jinja b/openhands/resolver/prompts/resolve/basic-with-tests.jinja index 4f3e5b2c0b..fbe444ab4a 100644 --- a/openhands/resolver/prompts/resolve/basic-with-tests.jinja +++ b/openhands/resolver/prompts/resolve/basic-with-tests.jinja @@ -2,4 +2,4 @@ Please fix the following issue for the repository in /workspace. An environment has been set up for you to start working. You may assume all necessary tools are installed. # Problem Statement -{{ body }} \ No newline at end of file +{{ body }} diff --git a/openhands/resolver/prompts/resolve/basic.jinja b/openhands/resolver/prompts/resolve/basic.jinja index 4f3e5b2c0b..fbe444ab4a 100644 --- a/openhands/resolver/prompts/resolve/basic.jinja +++ b/openhands/resolver/prompts/resolve/basic.jinja @@ -2,4 +2,4 @@ Please fix the following issue for the repository in /workspace. An environment has been set up for you to start working. You may assume all necessary tools are installed. # Problem Statement -{{ body }} \ No newline at end of file +{{ body }} diff --git a/openhands/runtime/browser/base64.py b/openhands/runtime/browser/base64.py index 94890e73c8..c5320aa8b8 100644 --- a/openhands/runtime/browser/base64.py +++ b/openhands/runtime/browser/base64.py @@ -1,7 +1,9 @@ -import io import base64 -from PIL import Image +import io + import numpy as np +from PIL import Image + def image_to_png_base64_url( image: np.ndarray | Image.Image, add_data_prefix: bool = False @@ -21,6 +23,7 @@ def image_to_png_base64_url( else f'{image_base64}' ) + def png_base64_url_to_image(png_base64_url: str) -> Image.Image: """Convert a base64 encoded png image url to a PIL Image.""" splited = png_base64_url.split(',') diff --git a/openhands/runtime/browser/browser_env.py b/openhands/runtime/browser/browser_env.py index e7087a1458..e3dfc4c7cc 100644 --- a/openhands/runtime/browser/browser_env.py +++ b/openhands/runtime/browser/browser_env.py @@ -12,13 +12,14 @@ from browsergym.utils.obs import flatten_dom_to_str, overlay_som from openhands.core.exceptions import BrowserInitException from openhands.core.logger import openhands_logger as logger +from openhands.runtime.browser.base64 import image_to_png_base64_url from openhands.utils.shutdown_listener import should_continue, should_exit from openhands.utils.tenacity_stop import stop_if_should_exit -from openhands.runtime.browser.base64 import image_to_png_base64_url BROWSER_EVAL_GET_GOAL_ACTION = 'GET_EVAL_GOAL' BROWSER_EVAL_GET_REWARDS_ACTION = 'GET_EVAL_REWARDS' + class BrowserEnv: def __init__(self, browsergym_eval_env: str | None = None): self.html_text_converter = self.get_html_text_converter() diff --git a/openhands/runtime/utils/log_capture.py b/openhands/runtime/utils/log_capture.py index 4c7c798dfb..9cca3658c7 100644 --- a/openhands/runtime/utils/log_capture.py +++ b/openhands/runtime/utils/log_capture.py @@ -6,22 +6,22 @@ from contextlib import asynccontextmanager @asynccontextmanager async def capture_logs(logger_name, level=logging.ERROR): logger = logging.getLogger(logger_name) - + # Store original handlers and level original_handlers = logger.handlers[:] original_level = logger.level - + # Set up capture log_capture = io.StringIO() handler = logging.StreamHandler(log_capture) handler.setLevel(level) - + logger.handlers = [handler] logger.setLevel(level) - + try: yield log_capture finally: # Restore original configuration logger.handlers = original_handlers - logger.setLevel(original_level) \ No newline at end of file + logger.setLevel(original_level) diff --git a/openhands/server/config/server_config.py b/openhands/server/config/server_config.py index 3ae9cf9910..bfdb62404b 100644 --- a/openhands/server/config/server_config.py +++ b/openhands/server/config/server_config.py @@ -22,7 +22,7 @@ class ServerConfig(ServerConfigInterface): 'openhands.storage.conversation.file_conversation_store.FileConversationStore' ) conversation_manager_class: str = os.environ.get( - "CONVERSATION_MANAGER_CLASS", + 'CONVERSATION_MANAGER_CLASS', 'openhands.server.conversation_manager.standalone_conversation_manager.StandaloneConversationManager', ) monitoring_listener_class: str = 'openhands.server.monitoring.MonitoringListener' diff --git a/openhands/server/data_models/agent_loop_info.py b/openhands/server/data_models/agent_loop_info.py index 894afe3693..3f743335b0 100644 --- a/openhands/server/data_models/agent_loop_info.py +++ b/openhands/server/data_models/agent_loop_info.py @@ -9,6 +9,7 @@ class AgentLoopInfo: """ Information about an agent loop - the URL on which to locate it and the event store """ + conversation_id: str url: str | None session_api_key: str | None diff --git a/openhands/server/routes/health.py b/openhands/server/routes/health.py index 7027115251..2a9d4bb102 100644 --- a/openhands/server/routes/health.py +++ b/openhands/server/routes/health.py @@ -1,4 +1,5 @@ import time + from fastapi import FastAPI, Request from openhands.runtime.utils.system_stats import get_system_stats @@ -6,17 +7,16 @@ from openhands.runtime.utils.system_stats import get_system_stats start_time = time.time() last_execution_time = start_time + def add_health_endpoints(app: FastAPI): @app.get('/alive') async def alive(): return {'status': 'ok'} - @app.get('/health') async def health() -> str: return 'OK' - @app.get('/server_info') async def get_server_info(): current_time = time.time() @@ -29,9 +29,8 @@ def add_health_endpoints(app: FastAPI): 'resources': get_system_stats(), } return response - - @app.middleware("http") + @app.middleware('http') async def update_last_execution_time(request: Request, call_next): global last_execution_time response = await call_next(request) diff --git a/openhands/server/routes/manage_conversations.py b/openhands/server/routes/manage_conversations.py index e8f77403c0..1053884cd4 100644 --- a/openhands/server/routes/manage_conversations.py +++ b/openhands/server/routes/manage_conversations.py @@ -23,7 +23,7 @@ from openhands.server.data_models.conversation_info_result_set import ( ConversationInfoResultSet, ) from openhands.server.dependencies import get_dependencies -from openhands.server.services.conversation import create_new_conversation +from openhands.server.services.conversation_service import create_new_conversation from openhands.server.shared import ( ConversationStoreImpl, config, @@ -103,8 +103,10 @@ async def new_conversation( if auth_type == AuthType.BEARER: conversation_trigger = ConversationTrigger.REMOTE_API_KEY - - if conversation_trigger == ConversationTrigger.REMOTE_API_KEY and not initial_user_msg: + if ( + conversation_trigger == ConversationTrigger.REMOTE_API_KEY + and not initial_user_msg + ): return JSONResponse( content={ 'status': 'error', @@ -193,19 +195,27 @@ async def search_conversations( conversation_ids = set( conversation.conversation_id for conversation in filtered_results ) - connection_ids_to_conversation_ids = await conversation_manager.get_connections(filter_to_sids=conversation_ids) - agent_loop_info = await conversation_manager.get_agent_loop_info(filter_to_sids=conversation_ids) - agent_loop_info_by_conversation_id = {info.conversation_id: info for info in agent_loop_info} + connection_ids_to_conversation_ids = await conversation_manager.get_connections( + filter_to_sids=conversation_ids + ) + agent_loop_info = await conversation_manager.get_agent_loop_info( + filter_to_sids=conversation_ids + ) + agent_loop_info_by_conversation_id = { + info.conversation_id: info for info in agent_loop_info + } result = ConversationInfoResultSet( results=await wait_all( _get_conversation_info( conversation=conversation, num_connections=sum( - 1 for conversation_id in connection_ids_to_conversation_ids.values() + 1 + for conversation_id in connection_ids_to_conversation_ids.values() if conversation_id == conversation.conversation_id ), - agent_loop_info=agent_loop_info_by_conversation_id.get(conversation.conversation_id), - + agent_loop_info=agent_loop_info_by_conversation_id.get( + conversation.conversation_id + ), ) for conversation in filtered_results ), @@ -221,10 +231,16 @@ async def get_conversation( ) -> ConversationInfo | None: try: metadata = await conversation_store.get_metadata(conversation_id) - num_connections = len(await conversation_manager.get_connections(filter_to_sids={conversation_id})) - agent_loop_infos = await conversation_manager.get_agent_loop_info(filter_to_sids={conversation_id}) + num_connections = len( + await conversation_manager.get_connections(filter_to_sids={conversation_id}) + ) + agent_loop_infos = await conversation_manager.get_agent_loop_info( + filter_to_sids={conversation_id} + ) agent_loop_info = agent_loop_infos[0] if agent_loop_infos else None - conversation_info = await _get_conversation_info(metadata, num_connections, agent_loop_info) + conversation_info = await _get_conversation_info( + metadata, num_connections, agent_loop_info + ) return conversation_info except FileNotFoundError: return None @@ -268,11 +284,15 @@ async def _get_conversation_info( selected_branch=conversation.selected_branch, git_provider=conversation.git_provider, status=( - agent_loop_info.status if agent_loop_info else ConversationStatus.STOPPED + agent_loop_info.status + if agent_loop_info + else ConversationStatus.STOPPED ), num_connections=num_connections, url=agent_loop_info.url if agent_loop_info else None, - session_api_key=agent_loop_info.session_api_key if agent_loop_info else None, + session_api_key=agent_loop_info.session_api_key + if agent_loop_info + else None, ) except Exception as e: logger.error( diff --git a/openhands/server/routes/secrets.py b/openhands/server/routes/secrets.py index 16761664a6..99823e3d96 100644 --- a/openhands/server/routes/secrets.py +++ b/openhands/server/routes/secrets.py @@ -218,7 +218,9 @@ async def create_custom_secret( ) -> JSONResponse: try: existing_secrets = await secrets_store.load() - custom_secrets = dict(existing_secrets.custom_secrets) if existing_secrets else {} + custom_secrets = ( + dict(existing_secrets.custom_secrets) if existing_secrets else {} + ) secret_name = incoming_secret.name secret_value = incoming_secret.value @@ -238,7 +240,9 @@ async def create_custom_secret( # Create a new UserSecrets that preserves provider tokens updated_user_secrets = UserSecrets( custom_secrets=custom_secrets, - provider_tokens=existing_secrets.provider_tokens if existing_secrets else {}, + provider_tokens=existing_secrets.provider_tokens + if existing_secrets + else {}, ) await secrets_store.store(updated_user_secrets) diff --git a/openhands/server/services/conversation.py b/openhands/server/services/conversation_service.py similarity index 100% rename from openhands/server/services/conversation.py rename to openhands/server/services/conversation_service.py diff --git a/openhands/server/user_auth/user_auth.py b/openhands/server/user_auth/user_auth.py index 6c40600b89..50e5fca273 100644 --- a/openhands/server/user_auth/user_auth.py +++ b/openhands/server/user_auth/user_auth.py @@ -7,8 +7,8 @@ from fastapi import Request from pydantic import SecretStr from openhands.integrations.provider import PROVIDER_TOKEN_TYPE -from openhands.server.shared import server_config from openhands.server.settings import Settings +from openhands.server.shared import server_config from openhands.storage.data_models.user_secrets import UserSecrets from openhands.storage.secrets.secrets_store import SecretsStore from openhands.storage.settings.settings_store import SettingsStore diff --git a/openhands/storage/data_models/user_secrets.py b/openhands/storage/data_models/user_secrets.py index 6ffe7ff9a2..76318d723f 100644 --- a/openhands/storage/data_models/user_secrets.py +++ b/openhands/storage/data_models/user_secrets.py @@ -137,7 +137,6 @@ class UserSecrets(BaseModel): new_data['custom_secrets'] = secrets return new_data - def set_event_stream_secrets(self, event_stream: EventStream) -> None: """ @@ -158,10 +157,9 @@ class UserSecrets(BaseModel): return secrets - def get_custom_secrets_descriptions(self) -> dict[str, str]: secrets = {} for secret_name, secret in self.custom_secrets.items(): secrets[secret_name] = secret.description - return secrets \ No newline at end of file + return secrets