CLI: bump agent-sdk (#11710)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Rohit Malhotra
2025-11-11 15:29:18 -05:00
committed by GitHub
parent 8b6521de62
commit 0a6b76ca2d
7 changed files with 62 additions and 44 deletions

View File

@@ -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(

View File

@@ -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)

View File

@@ -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

View File

@@ -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" }

View File

@@ -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"
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"

View File

@@ -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

16
openhands-cli/uv.lock generated
View File

@@ -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]]