Compare commits

..

1 Commits

Author SHA1 Message Date
rohitvinodmalhotra@gmail.com e5ea2ac478 Update pyproject.toml 2025-10-31 09:49:27 -04:00
9 changed files with 22 additions and 136 deletions
@@ -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
View File
@@ -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())
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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'],
+1 -1
View File
@@ -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"
}