mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5ea2ac478 |
@@ -71,14 +71,6 @@ jobs:
|
||||
|
||||
echo "✅ Build & test finished without ❌ markers"
|
||||
|
||||
- name: Verify binary files exist
|
||||
run: |
|
||||
if ! ls openhands-cli/dist/openhands* 1> /dev/null 2>&1; then
|
||||
echo "❌ No binaries found to upload!"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Found binaries to upload."
|
||||
|
||||
- name: Upload binary artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
+12
-16
@@ -20,6 +20,15 @@ from openhands_cli.locations import AGENT_SETTINGS_PATH, PERSISTENCE_DIR
|
||||
|
||||
from openhands.sdk import LLM
|
||||
|
||||
dummy_agent = get_default_cli_agent(
|
||||
llm=LLM(
|
||||
model='dummy-model',
|
||||
api_key='dummy-key',
|
||||
metadata=get_llm_metadata(model_name='dummy-model', llm_type='openhands'),
|
||||
),
|
||||
cli_mode=True,
|
||||
)
|
||||
|
||||
# =================================================
|
||||
# SECTION: Build Binary
|
||||
# =================================================
|
||||
@@ -117,7 +126,7 @@ def _is_welcome(line: str) -> bool:
|
||||
return any(marker in s for marker in WELCOME_MARKERS)
|
||||
|
||||
|
||||
def test_executable(dummy_agent) -> bool:
|
||||
def test_executable() -> bool:
|
||||
"""Test the built executable, measuring boot time and total test time."""
|
||||
print('🧪 Testing the built executable...')
|
||||
|
||||
@@ -265,14 +274,7 @@ def main() -> int:
|
||||
|
||||
# Test the executable
|
||||
if not args.no_test:
|
||||
dummy_agent = get_default_cli_agent(
|
||||
llm=LLM(
|
||||
model='dummy-model',
|
||||
api_key='dummy-key',
|
||||
metadata=get_llm_metadata(model_name='dummy-model', llm_type='openhands'),
|
||||
)
|
||||
)
|
||||
if not test_executable(dummy_agent):
|
||||
if not test_executable():
|
||||
print('❌ Executable test failed, build process failed')
|
||||
return 1
|
||||
|
||||
@@ -283,10 +285,4 @@ def main() -> int:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print('❌ Executable test failed')
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(main())
|
||||
|
||||
@@ -127,7 +127,7 @@ def run_cli_entry(resume_conversation_id: str | None = None) -> None:
|
||||
break
|
||||
|
||||
elif command == '/settings':
|
||||
settings_screen = SettingsScreen(runner.conversation if runner else None)
|
||||
settings_screen = SettingsScreen(conversation)
|
||||
settings_screen.display_settings()
|
||||
continue
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
|
||||
from openhands.sdk import LLM, BaseConversation, LLMSummarizingCondenser, LocalFileStore
|
||||
from openhands.sdk import LLM, BaseConversation, LocalFileStore
|
||||
from prompt_toolkit import HTML, print_formatted_text
|
||||
from prompt_toolkit.shortcuts import print_container
|
||||
from prompt_toolkit.widgets import Frame, TextArea
|
||||
@@ -33,6 +33,9 @@ class SettingsScreen:
|
||||
agent_spec = self.agent_store.load()
|
||||
if not agent_spec:
|
||||
return
|
||||
assert self.conversation is not None, (
|
||||
'Conversation must be set to display settings.'
|
||||
)
|
||||
|
||||
llm = agent_spec.llm
|
||||
advanced_llm_settings = True if llm.base_url else False
|
||||
@@ -59,20 +62,12 @@ class SettingsScreen:
|
||||
labels_and_values.extend(
|
||||
[
|
||||
(' API Key', '********' if llm.api_key else 'Not Set'),
|
||||
]
|
||||
)
|
||||
|
||||
if self.conversation:
|
||||
labels_and_values.extend([
|
||||
(
|
||||
' Confirmation Mode',
|
||||
'Enabled'
|
||||
if self.conversation.is_confirmation_mode_active
|
||||
else 'Disabled',
|
||||
)
|
||||
])
|
||||
|
||||
labels_and_values.extend([
|
||||
),
|
||||
(
|
||||
' Memory Condensation',
|
||||
'Enabled' if agent_spec.condenser else 'Disabled',
|
||||
@@ -158,7 +153,7 @@ class SettingsScreen:
|
||||
api_key = prompt_api_key(
|
||||
step_counter,
|
||||
custom_model.split('/')[0] if len(custom_model.split('/')) > 1 else '',
|
||||
self.conversation.state.agent.llm.api_key if self.conversation else None,
|
||||
self.conversation.agent.llm.api_key if self.conversation else None,
|
||||
escapable=escapable,
|
||||
)
|
||||
memory_condensation = choose_memory_condensation(step_counter)
|
||||
@@ -187,14 +182,7 @@ class SettingsScreen:
|
||||
if not agent:
|
||||
agent = get_default_cli_agent(llm=llm)
|
||||
|
||||
# Must update all LLMs
|
||||
agent = agent.model_copy(update={'llm': llm})
|
||||
condenser = LLMSummarizingCondenser(
|
||||
llm=llm.model_copy(
|
||||
update={"usage_id": "condenser"}
|
||||
)
|
||||
)
|
||||
agent = agent.model_copy(update={'condenser': condenser})
|
||||
self.agent_store.save(agent)
|
||||
|
||||
def _save_advanced_settings(
|
||||
|
||||
@@ -4,7 +4,7 @@ requires = [ "hatchling>=1.25" ]
|
||||
|
||||
[project]
|
||||
name = "openhands"
|
||||
version = "1.0.5"
|
||||
version = "1.0.4"
|
||||
description = "OpenHands CLI - Terminal User Interface for OpenHands AI Agent"
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
"""Test for the /settings command functionality."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
from prompt_toolkit.input.defaults import create_pipe_input
|
||||
from prompt_toolkit.output.defaults import DummyOutput
|
||||
|
||||
from openhands_cli.agent_chat import run_cli_entry
|
||||
from openhands_cli.user_actions import UserConfirmation
|
||||
|
||||
|
||||
@patch('openhands_cli.agent_chat.exit_session_confirmation')
|
||||
@patch('openhands_cli.agent_chat.get_session_prompter')
|
||||
@patch('openhands_cli.agent_chat.setup_conversation')
|
||||
@patch('openhands_cli.agent_chat.verify_agent_exists_or_setup_agent')
|
||||
@patch('openhands_cli.agent_chat.ConversationRunner')
|
||||
@patch('openhands_cli.agent_chat.SettingsScreen')
|
||||
def test_settings_command_works_without_conversation(
|
||||
mock_settings_screen_class,
|
||||
mock_runner_cls,
|
||||
mock_verify_agent,
|
||||
mock_setup_conversation,
|
||||
mock_get_session_prompter,
|
||||
mock_exit_confirm,
|
||||
):
|
||||
"""Test that /settings command works when no conversation is active (bug fix scenario)."""
|
||||
# Auto-accept the exit prompt to avoid interactive UI
|
||||
mock_exit_confirm.return_value = UserConfirmation.ACCEPT
|
||||
|
||||
# Mock agent verification to succeed
|
||||
mock_agent = MagicMock()
|
||||
mock_verify_agent.return_value = mock_agent
|
||||
|
||||
# Mock the SettingsScreen instance
|
||||
mock_settings_screen = MagicMock()
|
||||
mock_settings_screen_class.return_value = mock_settings_screen
|
||||
|
||||
# No runner initially (simulates starting CLI without a conversation)
|
||||
mock_runner_cls.return_value = None
|
||||
|
||||
# Real session fed by a pipe
|
||||
from openhands_cli.user_actions.utils import get_session_prompter as real_get_session_prompter
|
||||
with create_pipe_input() as pipe:
|
||||
output = DummyOutput()
|
||||
session = real_get_session_prompter(input=pipe, output=output)
|
||||
mock_get_session_prompter.return_value = session
|
||||
|
||||
# Trigger /settings, then /exit (exit will be auto-accepted)
|
||||
for ch in "/settings\r/exit\r":
|
||||
pipe.send_text(ch)
|
||||
|
||||
run_cli_entry(None)
|
||||
|
||||
# Assert SettingsScreen was created with None conversation (the bug fix)
|
||||
mock_settings_screen_class.assert_called_once_with(None)
|
||||
|
||||
# Assert display_settings was called (settings screen was shown)
|
||||
mock_settings_screen.display_settings.assert_called_once()
|
||||
@@ -121,38 +121,6 @@ def test_update_existing_settings_workflow(tmp_path: Path):
|
||||
assert True # If we get here, the workflow completed successfully
|
||||
|
||||
|
||||
def test_all_llms_in_agent_are_updated():
|
||||
"""Test that modifying LLM settings creates multiple LLMs with same API key but different usage_ids."""
|
||||
# Create a screen with existing agent settings
|
||||
screen = SettingsScreen(conversation=None)
|
||||
initial_llm = LLM(model='openai/gpt-3.5-turbo', api_key=SecretStr('sk-initial'), usage_id='test-service')
|
||||
initial_agent = get_default_cli_agent(llm=initial_llm)
|
||||
|
||||
# Mock the agent store to return the initial agent and capture the save call
|
||||
with (
|
||||
patch.object(screen.agent_store, 'load', return_value=initial_agent),
|
||||
patch.object(screen.agent_store, 'save') as mock_save
|
||||
):
|
||||
# Modify the LLM settings with new API key
|
||||
screen._save_llm_settings(model='openai/gpt-4o-mini', api_key='sk-updated-123')
|
||||
mock_save.assert_called_once()
|
||||
|
||||
# Get the saved agent from the mock
|
||||
saved_agent = mock_save.call_args[0][0]
|
||||
all_llms = list(saved_agent.get_all_llms())
|
||||
assert len(all_llms) >= 2, f"Expected at least 2 LLMs, got {len(all_llms)}"
|
||||
|
||||
# Verify all LLMs have the same API key
|
||||
api_keys = [llm.api_key.get_secret_value() for llm in all_llms]
|
||||
assert all(api_key == 'sk-updated-123' for api_key in api_keys), \
|
||||
f"Not all LLMs have the same API key: {api_keys}"
|
||||
|
||||
# Verify none of the usage_id attributes match
|
||||
usage_ids = [llm.usage_id for llm in all_llms]
|
||||
assert len(set(usage_ids)) == len(usage_ids), \
|
||||
f"Some usage_ids are duplicated: {usage_ids}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'step_to_cancel',
|
||||
['type', 'provider', 'model', 'apikey', 'save'],
|
||||
|
||||
Generated
+1
-1
@@ -1828,7 +1828,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "openhands"
|
||||
version = "1.0.5"
|
||||
version = "1.0.3"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "openhands-sdk" },
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"workbench.colorTheme": "Default Dark Modern",
|
||||
"workbench.startupEditor": "none",
|
||||
"chat.commandCenter.enabled": false
|
||||
"workbench.startupEditor": "none"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user