From 0a6b76ca2d1a209315ae4fffea038e4c316809b2 Mon Sep 17 00:00:00 2001 From: Rohit Malhotra Date: Tue, 11 Nov 2025 15:29:18 -0500 Subject: [PATCH] CLI: bump agent-sdk (#11710) Co-authored-by: openhands --- openhands-cli/openhands_cli/setup.py | 13 +++--- .../openhands_cli/tui/settings/store.py | 10 +++++ openhands-cli/openhands_cli/utils.py | 7 --- openhands-cli/pyproject.toml | 8 ++-- .../test_default_agent_security_analyzer.py | 43 ++++++++++++------- .../tests/test_conversation_runner.py | 9 ++-- openhands-cli/uv.lock | 16 +++---- 7 files changed, 62 insertions(+), 44 deletions(-) diff --git a/openhands-cli/openhands_cli/setup.py b/openhands-cli/openhands_cli/setup.py index 8897eefdb3..91b7ab9464 100644 --- a/openhands-cli/openhands_cli/setup.py +++ b/openhands-cli/openhands_cli/setup.py @@ -1,6 +1,7 @@ import uuid from openhands.sdk.conversation import visualizer +from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer from prompt_toolkit import HTML, print_formatted_text from openhands.sdk import Agent, BaseConversation, Conversation, Workspace @@ -74,11 +75,7 @@ def setup_conversation( agent = load_agent_specs(str(conversation_id)) - if not include_security_analyzer: - # Remove security analyzer from agent spec - agent = agent.model_copy( - update={"security_analyzer": None} - ) + # Create conversation - agent context is now set in AgentStore.load() conversation: BaseConversation = Conversation( @@ -90,7 +87,11 @@ def setup_conversation( visualizer=CLIVisualizer ) - if include_security_analyzer: + # Security analyzer is set though conversation API now + if not include_security_analyzer: + conversation.set_security_analyzer(None) + else: + conversation.set_security_analyzer(LLMSecurityAnalyzer()) conversation.set_confirmation_policy(AlwaysConfirm()) print_formatted_text( diff --git a/openhands-cli/openhands_cli/tui/settings/store.py b/openhands-cli/openhands_cli/tui/settings/store.py index 3a292019a8..018a7484e0 100644 --- a/openhands-cli/openhands_cli/tui/settings/store.py +++ b/openhands-cli/openhands_cli/tui/settings/store.py @@ -38,6 +38,16 @@ class AgentStore: str_spec = self.file_store.read(AGENT_SETTINGS_PATH) agent = Agent.model_validate_json(str_spec) + + # Temporary to remove security analyzer from agent specs + # Security analyzer is set via conversation API now + # Doing this so that deprecation warning is thrown only the first time running CLI + if agent.security_analyzer: + agent = agent.model_copy( + update={"security_analyzer": None} + ) + self.save(agent) + # Update tools with most recent working directory updated_tools = get_default_tools(enable_browser=False) diff --git a/openhands-cli/openhands_cli/utils.py b/openhands-cli/openhands_cli/utils.py index b5bbc44104..50571cd7be 100644 --- a/openhands-cli/openhands_cli/utils.py +++ b/openhands-cli/openhands_cli/utils.py @@ -2,7 +2,6 @@ import os from typing import Any -from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer from openhands.tools.preset import get_default_agent from openhands.sdk import LLM @@ -67,10 +66,4 @@ def get_default_cli_agent( cli_mode=True ) - agent = agent.model_copy( - update={ - 'security_analyzer': LLMSecurityAnalyzer() - } - ) - return agent diff --git a/openhands-cli/pyproject.toml b/openhands-cli/pyproject.toml index e042c12572..5c035ec57d 100644 --- a/openhands-cli/pyproject.toml +++ b/openhands-cli/pyproject.toml @@ -18,8 +18,8 @@ classifiers = [ # Using Git URLs for dependencies so installs from PyPI pull from GitHub # TODO: pin package versions once agent-sdk has published PyPI packages dependencies = [ - "openhands-sdk==1", - "openhands-tools==1", + "openhands-sdk==1.1", + "openhands-tools==1.1", "prompt-toolkit>=3", "typer>=0.17.4", ] @@ -102,5 +102,5 @@ ignore_missing_imports = true # UNCOMMENT TO USE EXACT COMMIT FROM AGENT-SDK # [tool.uv.sources] -# openhands-sdk = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-sdk", rev = "aaa0066ee078688e015fcad590393fe6771c10a1" } -# openhands-tools = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-tools", rev = "aaa0066ee078688e015fcad590393fe6771c10a1" } +# openhands-sdk = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-sdk", rev = "7b695dc519084e75c482b34473e714845d6cef92" } +# openhands-tools = { git = "https://github.com/OpenHands/agent-sdk.git", subdirectory = "openhands-tools", rev = "7b695dc519084e75c482b34473e714845d6cef92" } diff --git a/openhands-cli/tests/settings/test_default_agent_security_analyzer.py b/openhands-cli/tests/settings/test_default_agent_security_analyzer.py index 0c23afb866..61ab9b2a2f 100644 --- a/openhands-cli/tests/settings/test_default_agent_security_analyzer.py +++ b/openhands-cli/tests/settings/test_default_agent_security_analyzer.py @@ -1,15 +1,16 @@ -"""Test that first-time settings screen usage creates a default agent with security analyzer.""" +"""Test that first-time settings screen usage creates a default agent and conversation with security analyzer.""" from unittest.mock import patch import pytest from openhands_cli.tui.settings.settings_screen import SettingsScreen from openhands_cli.user_actions.settings_action import SettingsType -from openhands.sdk import LLM +from openhands.sdk import LLM, Conversation, Workspace +from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer from pydantic import SecretStr -def test_first_time_settings_creates_default_agent_with_security_analyzer(): - """Test that using the settings screen for the first time creates a default agent with a non-None security analyzer.""" +def test_first_time_settings_creates_default_agent_and_conversation_with_security_analyzer(): + """Test that using the settings screen for the first time creates a default agent and conversation with security analyzer.""" # Create a settings screen instance (no conversation initially) screen = SettingsScreen(conversation=None) @@ -50,17 +51,20 @@ def test_first_time_settings_creates_default_agent_with_security_analyzer(): assert saved_agent.llm.model == 'openai/gpt-4o-mini', f"Expected model 'openai/gpt-4o-mini', got '{saved_agent.llm.model}'" assert saved_agent.llm.api_key.get_secret_value() == 'sk-test-key-123', "API key should match the provided value" - # Verify that the agent has a security analyzer and it's not None - assert hasattr(saved_agent, 'security_analyzer'), "Agent should have a security_analyzer attribute" - assert saved_agent.security_analyzer is not None, "Security analyzer should not be None" + # Test that a conversation can be created with the agent and security analyzer can be set + conversation = Conversation(agent=saved_agent, workspace=Workspace(working_dir='/tmp')) - # Verify the security analyzer has the expected type/kind - assert hasattr(saved_agent.security_analyzer, 'kind'), "Security analyzer should have a 'kind' attribute" - assert saved_agent.security_analyzer.kind == 'LLMSecurityAnalyzer', f"Expected security analyzer kind 'LLMSecurityAnalyzer', got '{saved_agent.security_analyzer.kind}'" + # Set security analyzer using the new API + security_analyzer = LLMSecurityAnalyzer() + conversation.set_security_analyzer(security_analyzer) + + # Verify that the security analyzer was set correctly + assert conversation.state.security_analyzer is not None, "Conversation should have a security analyzer" + assert conversation.state.security_analyzer.kind == 'LLMSecurityAnalyzer', f"Expected security analyzer kind 'LLMSecurityAnalyzer', got '{conversation.state.security_analyzer.kind}'" def test_first_time_settings_with_advanced_configuration(): - """Test that advanced settings also create a default agent with security analyzer.""" + """Test that advanced settings also create a default agent and conversation with security analyzer.""" screen = SettingsScreen(conversation=None) @@ -94,11 +98,20 @@ def test_first_time_settings_with_advanced_configuration(): saved_agent = screen.agent_store.load() - # Verify agent creation and security analyzer + # Verify agent creation assert saved_agent is not None, "Agent should be created with advanced settings" - assert saved_agent.security_analyzer is not None, "Security analyzer should not be None in advanced settings" - assert saved_agent.security_analyzer.kind == 'LLMSecurityAnalyzer', "Security analyzer should be LLMSecurityAnalyzer" # Verify advanced settings were applied assert saved_agent.llm.model == 'anthropic/claude-3-5-sonnet', "Custom model should be set" - assert saved_agent.llm.base_url == 'https://api.anthropic.com', "Base URL should be set" \ No newline at end of file + assert saved_agent.llm.base_url == 'https://api.anthropic.com', "Base URL should be set" + + # Test that a conversation can be created with the agent and security analyzer can be set + conversation = Conversation(agent=saved_agent, workspace=Workspace(working_dir='/tmp')) + + # Set security analyzer using the new API + security_analyzer = LLMSecurityAnalyzer() + conversation.set_security_analyzer(security_analyzer) + + # Verify that the security analyzer was set correctly + assert conversation.state.security_analyzer is not None, "Conversation should have a security analyzer" + assert conversation.state.security_analyzer.kind == 'LLMSecurityAnalyzer', "Security analyzer should be LLMSecurityAnalyzer" \ No newline at end of file diff --git a/openhands-cli/tests/test_conversation_runner.py b/openhands-cli/tests/test_conversation_runner.py index 8314cccf2f..ebc7f04c44 100644 --- a/openhands-cli/tests/test_conversation_runner.py +++ b/openhands-cli/tests/test_conversation_runner.py @@ -108,15 +108,15 @@ class TestConversationRunner: 3. If not paused, we should still ask for confirmation on actions 4. If deferred no run call to agent should be made 5. If accepted, run call to agent should be made - """ if final_status == ConversationExecutionStatus.FINISHED: agent.finish_on_step = 1 - # Add a mock security analyzer to enable confirmation mode - agent.security_analyzer = MagicMock() - 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,6 +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-cli/uv.lock b/openhands-cli/uv.lock index 7714eb18df..ef4f2de5f8 100644 --- a/openhands-cli/uv.lock +++ b/openhands-cli/uv.lock @@ -1929,8 +1929,8 @@ dev = [ [package.metadata] requires-dist = [ - { name = "openhands-sdk", specifier = "==1" }, - { name = "openhands-tools", specifier = "==1" }, + { name = "openhands-sdk", specifier = "==1.1.0" }, + { name = "openhands-tools", specifier = "==1.1.0" }, { name = "prompt-toolkit", specifier = ">=3" }, { name = "typer", specifier = ">=0.17.4" }, ] @@ -1953,7 +1953,7 @@ dev = [ [[package]] name = "openhands-sdk" -version = "1.0.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastmcp" }, @@ -1966,14 +1966,14 @@ dependencies = [ { name = "tenacity" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/58/d6117840a14d013176a7a490a74295dffac64b44dc098532d4e8526c9a87/openhands_sdk-1.0.0.tar.gz", hash = "sha256:7c3a0d77d48d7eceaa77fda90ac654697ce916431b5c905d10d9ab6c07609a1a", size = 160726, upload-time = "2025-11-06T17:05:44.545Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/b2/97d9deb743b266683f3e70cebaa1d34ee247c019f7d6e42c2f5de529cb47/openhands_sdk-1.1.0.tar.gz", hash = "sha256:855e0d8f3657205e4119e50520c17e65b3358b1a923f7a051a82512a54bf426c", size = 166636, upload-time = "2025-11-11T19:07:04.249Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/9b/4d4c356ed50e6ad87e6dc8f87af1966c51c55a22955cebd632bf62040e5b/openhands_sdk-1.0.0-py3-none-any.whl", hash = "sha256:73916e22783e2c8500f19765fa340631a0e47ae9a3c5e40fb8411ecab4a1f49a", size = 214807, upload-time = "2025-11-06T17:05:43.474Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9f/a97a10447f3be53df4639e43748c4178853e958df07ba74890f4968829d6/openhands_sdk-1.1.0-py3-none-any.whl", hash = "sha256:4a984ce1687a48cf99a67fdf3d37b116f8b2840743d4807810b5024af6a1d57e", size = 221594, upload-time = "2025-11-11T19:07:02.847Z" }, ] [[package]] name = "openhands-tools" -version = "1.0.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bashlex" }, @@ -1985,9 +1985,9 @@ dependencies = [ { name = "openhands-sdk" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/49/3bad4d8283c76f72dacfde8fece9d1190774c87c40a011075868e8d18cbf/openhands_tools-1.0.0.tar.gz", hash = "sha256:f6bc8647149d541730520f1aeb409cd9eac96d796d19e39a40f300dcd2b0284c", size = 61997, upload-time = "2025-11-06T17:05:46.455Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/89/e2c5fc2d9e8dc6840ef2891ff6f76b9769b50a4c508fd3a626c1ab476fb1/openhands_tools-1.1.0.tar.gz", hash = "sha256:c2fadaa4f4e16e9a3df5781ea847565dcae7171584f09ef7c0e1d97c8dfc83f6", size = 62818, upload-time = "2025-11-11T19:07:06.527Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/15/23c5650a9470f9c125288508bf966e6b2ece479f5407801aa7fdda2ba5a0/openhands_tools-1.0.0-py3-none-any.whl", hash = "sha256:21a4ff3f37a3c71edd17b861fe1a9b86cc744ad9dc8a3626898ecdeeea7ae30f", size = 84232, upload-time = "2025-11-06T17:05:45.527Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a3/e58d75b7bd8d5dfbe063fcfaaadbdfd24fd511d633a528cefd29f0e01056/openhands_tools-1.1.0-py3-none-any.whl", hash = "sha256:767d6746f05edade49263aa24450a037485a3dc23379f56917ef19aad22033f9", size = 85062, upload-time = "2025-11-11T19:07:05.315Z" }, ] [[package]]