diff --git a/enterprise/poetry.lock b/enterprise/poetry.lock index cb3bfc2c3e..c359147ba6 100644 --- a/enterprise/poetry.lock +++ b/enterprise/poetry.lock @@ -5829,6 +5829,7 @@ files = [ {file = "openhands_agent_server-1.1.0-py3-none-any.whl", hash = "sha256:59a856883df23488c0723e47655ef21649a321fcd4709a25a4690866eff6ac88"}, {file = "openhands_agent_server-1.1.0.tar.gz", hash = "sha256:e39bebd39afd45cfcfd765005e7c4e5409e46678bd7612ae20bae79f7057b935"}, ] +develop = false [package.dependencies] aiosqlite = ">=0.19" @@ -5841,9 +5842,16 @@ uvicorn = ">=0.31.1" websockets = ">=12" wsproto = ">=1.2.0" +[package.source] +type = "git" +url = "https://github.com/OpenHands/agent-sdk.git" +reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +resolved_reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +subdirectory = "openhands-agent-server" + [[package]] name = "openhands-ai" -version = "0.0.0-post.5525+0b6631523" +version = "0.0.0-post.5576+ed2ac6040" description = "OpenHands: Code Less, Make More" optional = false python-versions = "^3.12,<3.14" @@ -5860,6 +5868,7 @@ bashlex = "^0.18" boto3 = "*" browsergym-core = "0.13.3" deprecated = "*" +deprecation = "^2.1.0" dirhash = "*" docker = "*" fastapi = "*" @@ -5951,8 +5960,10 @@ files = [ {file = "openhands_sdk-1.1.0-py3-none-any.whl", hash = "sha256:4a984ce1687a48cf99a67fdf3d37b116f8b2840743d4807810b5024af6a1d57e"}, {file = "openhands_sdk-1.1.0.tar.gz", hash = "sha256:855e0d8f3657205e4119e50520c17e65b3358b1a923f7a051a82512a54bf426c"}, ] +develop = false [package.dependencies] +deprecation = ">=2.1.0" fastmcp = ">=2.11.3" httpx = ">=0.27.0" litellm = ">=1.77.7.dev9" @@ -5966,6 +5977,13 @@ websockets = ">=12" [package.extras] boto3 = ["boto3 (>=1.35.0)"] +[package.source] +type = "git" +url = "https://github.com/OpenHands/agent-sdk.git" +reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +resolved_reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +subdirectory = "openhands-sdk" + [[package]] name = "openhands-tools" version = "1.1.0" @@ -5977,6 +5995,7 @@ files = [ {file = "openhands_tools-1.1.0-py3-none-any.whl", hash = "sha256:767d6746f05edade49263aa24450a037485a3dc23379f56917ef19aad22033f9"}, {file = "openhands_tools-1.1.0.tar.gz", hash = "sha256:c2fadaa4f4e16e9a3df5781ea847565dcae7171584f09ef7c0e1d97c8dfc83f6"}, ] +develop = false [package.dependencies] bashlex = ">=0.18" @@ -5988,6 +6007,13 @@ libtmux = ">=0.46.2" openhands-sdk = "*" pydantic = ">=2.11.7" +[package.source] +type = "git" +url = "https://github.com/OpenHands/agent-sdk.git" +reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +resolved_reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +subdirectory = "openhands-tools" + [[package]] name = "openpyxl" version = "3.1.5" diff --git a/openhands-cli/tests/test_conversation_runner.py b/openhands-cli/tests/test_conversation_runner.py index ebc7f04c44..ef085fa12b 100644 --- a/openhands-cli/tests/test_conversation_runner.py +++ b/openhands-cli/tests/test_conversation_runner.py @@ -113,10 +113,10 @@ class TestConversationRunner: agent.finish_on_step = 1 convo = Conversation(agent) - + # Set security analyzer using the new API to enable confirmation mode convo.set_security_analyzer(MagicMock()) - + convo.state.execution_status = ( ConversationExecutionStatus.WAITING_FOR_CONFIRMATION ) @@ -127,7 +127,7 @@ class TestConversationRunner: cr, '_handle_confirmation_request', return_value=confirmation ) as mock_confirmation_request: cr.process_message(message=None) - + mock_confirmation_request.assert_called_once() assert agent.step_count == expected_run_calls assert convo.state.execution_status == final_status diff --git a/openhands/app_server/sandbox/sandbox_spec_service.py b/openhands/app_server/sandbox/sandbox_spec_service.py index dad14297c5..997cbe5351 100644 --- a/openhands/app_server/sandbox/sandbox_spec_service.py +++ b/openhands/app_server/sandbox/sandbox_spec_service.py @@ -11,7 +11,7 @@ from openhands.sdk.utils.models import DiscriminatedUnionMixin # The version of the agent server to use for deployments. # Typically this will be the same as the values from the pyproject.toml -AGENT_SERVER_IMAGE = 'ghcr.io/openhands/agent-server:4e2ecd8-python' +AGENT_SERVER_IMAGE = 'ghcr.io/openhands/agent-server:15f565b-python' class SandboxSpecService(ABC): diff --git a/openhands/server/routes/manage_conversations.py b/openhands/server/routes/manage_conversations.py index 56f6b95f6c..babbc48654 100644 --- a/openhands/server/routes/manage_conversations.py +++ b/openhands/server/routes/manage_conversations.py @@ -9,6 +9,7 @@ from datetime import datetime, timedelta, timezone from typing import Annotated import base62 +import httpx from fastapi import APIRouter, Depends, Query, Request, status from fastapi.responses import JSONResponse from jinja2 import Environment, FileSystemLoader @@ -28,10 +29,14 @@ from openhands.app_server.config import ( depends_app_conversation_info_service, depends_app_conversation_service, depends_db_session, + depends_httpx_client, depends_sandbox_service, ) from openhands.app_server.sandbox.sandbox_service import SandboxService from openhands.app_server.services.db_session_injector import set_db_session_keep_open +from openhands.app_server.services.httpx_client_injector import ( + set_httpx_client_keep_open, +) from openhands.core.config.llm_config import LLMConfig from openhands.core.config.mcp_config import MCPConfig from openhands.core.logger import openhands_logger as logger @@ -105,6 +110,7 @@ app_conversation_service_dependency = depends_app_conversation_service() app_conversation_info_service_dependency = depends_app_conversation_info_service() sandbox_service_dependency = depends_sandbox_service() db_session_dependency = depends_db_session() +httpx_client_dependency = depends_httpx_client() def _filter_conversations_by_age( @@ -487,8 +493,11 @@ async def delete_conversation( app_conversation_info_service: AppConversationInfoService = app_conversation_info_service_dependency, sandbox_service: SandboxService = sandbox_service_dependency, db_session: AsyncSession = db_session_dependency, + httpx_client: httpx.AsyncClient = httpx_client_dependency, ) -> bool: set_db_session_keep_open(request.state, True) + set_httpx_client_keep_open(request.state, True) + # Try V1 conversation first v1_result = await _try_delete_v1_conversation( conversation_id, @@ -496,6 +505,7 @@ async def delete_conversation( app_conversation_info_service, sandbox_service, db_session, + httpx_client, ) if v1_result is not None: return v1_result @@ -510,6 +520,7 @@ async def _try_delete_v1_conversation( app_conversation_info_service: AppConversationInfoService, sandbox_service: SandboxService, db_session: AsyncSession, + httpx_client: httpx.AsyncClient, ) -> bool | None: """Try to delete a V1 conversation. Returns None if not a V1 conversation.""" result = None @@ -527,10 +538,17 @@ async def _try_delete_v1_conversation( result = await app_conversation_service.delete_app_conversation( app_conversation_info.id ) + + # Manually commit so that the conversation will vanish from the list + await db_session.commit() + # Delete the sandbox in the background asyncio.create_task( - _delete_sandbox_and_close_connection( - sandbox_service, app_conversation_info.sandbox_id, db_session + _delete_sandbox_and_close_connections( + sandbox_service, + app_conversation_info.sandbox_id, + db_session, + httpx_client, ) ) except (ValueError, TypeError): @@ -543,14 +561,22 @@ async def _try_delete_v1_conversation( return result -async def _delete_sandbox_and_close_connection( - sandbox_service: SandboxService, sandbox_id: str, db_session: AsyncSession +async def _delete_sandbox_and_close_connections( + sandbox_service: SandboxService, + sandbox_id: str, + db_session: AsyncSession, + httpx_client: httpx.AsyncClient, ): try: await sandbox_service.delete_sandbox(sandbox_id) await db_session.commit() finally: - await db_session.aclose() + await asyncio.gather( + *[ + db_session.aclose(), + httpx_client.aclose(), + ] + ) async def _delete_v0_conversation(conversation_id: str, user_id: str | None) -> bool: diff --git a/poetry.lock b/poetry.lock index b1d9684299..0cc47afea7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2408,6 +2408,21 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] +[[package]] +name = "deprecation" +version = "2.1.0" +description = "A library to handle automated deprecations" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, + {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, +] + +[package.dependencies] +packaging = "*" + [[package]] name = "dill" version = "0.3.8" @@ -7338,6 +7353,7 @@ files = [ {file = "openhands_agent_server-1.1.0-py3-none-any.whl", hash = "sha256:59a856883df23488c0723e47655ef21649a321fcd4709a25a4690866eff6ac88"}, {file = "openhands_agent_server-1.1.0.tar.gz", hash = "sha256:e39bebd39afd45cfcfd765005e7c4e5409e46678bd7612ae20bae79f7057b935"}, ] +develop = false [package.dependencies] aiosqlite = ">=0.19" @@ -7350,6 +7366,13 @@ uvicorn = ">=0.31.1" websockets = ">=12" wsproto = ">=1.2.0" +[package.source] +type = "git" +url = "https://github.com/OpenHands/agent-sdk.git" +reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +resolved_reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +subdirectory = "openhands-agent-server" + [[package]] name = "openhands-sdk" version = "1.1.0" @@ -7361,8 +7384,10 @@ files = [ {file = "openhands_sdk-1.1.0-py3-none-any.whl", hash = "sha256:4a984ce1687a48cf99a67fdf3d37b116f8b2840743d4807810b5024af6a1d57e"}, {file = "openhands_sdk-1.1.0.tar.gz", hash = "sha256:855e0d8f3657205e4119e50520c17e65b3358b1a923f7a051a82512a54bf426c"}, ] +develop = false [package.dependencies] +deprecation = ">=2.1.0" fastmcp = ">=2.11.3" httpx = ">=0.27.0" litellm = ">=1.77.7.dev9" @@ -7376,6 +7401,13 @@ websockets = ">=12" [package.extras] boto3 = ["boto3 (>=1.35.0)"] +[package.source] +type = "git" +url = "https://github.com/OpenHands/agent-sdk.git" +reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +resolved_reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +subdirectory = "openhands-sdk" + [[package]] name = "openhands-tools" version = "1.1.0" @@ -7387,6 +7419,7 @@ files = [ {file = "openhands_tools-1.1.0-py3-none-any.whl", hash = "sha256:767d6746f05edade49263aa24450a037485a3dc23379f56917ef19aad22033f9"}, {file = "openhands_tools-1.1.0.tar.gz", hash = "sha256:c2fadaa4f4e16e9a3df5781ea847565dcae7171584f09ef7c0e1d97c8dfc83f6"}, ] +develop = false [package.dependencies] bashlex = ">=0.18" @@ -7398,6 +7431,13 @@ libtmux = ">=0.46.2" openhands-sdk = "*" pydantic = ">=2.11.7" +[package.source] +type = "git" +url = "https://github.com/OpenHands/agent-sdk.git" +reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +resolved_reference = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" +subdirectory = "openhands-tools" + [[package]] name = "openpyxl" version = "3.1.5" @@ -16729,4 +16769,4 @@ third-party-runtimes = ["daytona", "e2b-code-interpreter", "modal", "runloop-api [metadata] lock-version = "2.1" python-versions = "^3.12,<3.14" -content-hash = "0fe5bab6aeb5ebce4588b30cfcf491af4cc9d9b9cd5160e67c8a055d9db276fc" +content-hash = "44c6c1f432337d216b70a6654fb0cd20410ddeb56485999859032aec53e90458" diff --git a/pyproject.toml b/pyproject.toml index 28b7b03f9a..0c45135d08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,9 +113,9 @@ e2b-code-interpreter = { version = "^2.0.0", optional = true } pybase62 = "^1.0.0" # V1 dependencies -#openhands-agent-server = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-agent-server", rev = "f3c0c19cd134fbda84e07f152897a6d61e1e46c5" } -#openhands-sdk = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-sdk", rev = "f3c0c19cd134fbda84e07f152897a6d61e1e46c5" } -#openhands-tools = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-tools", rev = "f3c0c19cd134fbda84e07f152897a6d61e1e46c5" } +#openhands-agent-server = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-agent-server", rev = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" } +#openhands-sdk = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-sdk", rev = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" } +#openhands-tools = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-tools", rev = "15f565b8ac38876e40dc05c08e2b04ccaae4a66d" } openhands-sdk = "1.1.0" openhands-agent-server = "1.1.0" openhands-tools = "1.1.0" @@ -123,6 +123,7 @@ python-jose = { version = ">=3.3", extras = [ "cryptography" ] } sqlalchemy = { extras = [ "asyncio" ], version = "^2.0.40" } pg8000 = "^1.31.5" asyncpg = "^0.30.0" +deprecation = "^2.1.0" lmnr = "^0.7.20" [tool.poetry.extras]