openhands 1.0.4__tar.gz → 1.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of openhands might be problematic. Click here for more details.

Files changed (61) hide show
  1. {openhands-1.0.4 → openhands-1.0.5}/PKG-INFO +1 -1
  2. {openhands-1.0.4 → openhands-1.0.5}/build.py +16 -12
  3. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/agent_chat.py +1 -1
  4. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/tui/settings/settings_screen.py +18 -6
  5. {openhands-1.0.4 → openhands-1.0.5}/pyproject.toml +1 -1
  6. openhands-1.0.5/tests/commands/test_settings_command.py +57 -0
  7. {openhands-1.0.4 → openhands-1.0.5}/tests/settings/test_settings_workflow.py +32 -0
  8. {openhands-1.0.4 → openhands-1.0.5}/uv.lock +1 -1
  9. {openhands-1.0.4 → openhands-1.0.5}/.gitignore +0 -0
  10. {openhands-1.0.4 → openhands-1.0.5}/Makefile +0 -0
  11. {openhands-1.0.4 → openhands-1.0.5}/README.md +0 -0
  12. {openhands-1.0.4 → openhands-1.0.5}/build.sh +0 -0
  13. {openhands-1.0.4 → openhands-1.0.5}/hooks/rthook_profile_imports.py +0 -0
  14. {openhands-1.0.4 → openhands-1.0.5}/openhands.spec +0 -0
  15. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/__init__.py +0 -0
  16. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/argparsers/main_parser.py +0 -0
  17. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/argparsers/serve_parser.py +0 -0
  18. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/gui_launcher.py +0 -0
  19. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/listeners/__init__.py +0 -0
  20. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/listeners/loading_listener.py +0 -0
  21. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/listeners/pause_listener.py +0 -0
  22. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/locations.py +0 -0
  23. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/pt_style.py +0 -0
  24. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/runner.py +0 -0
  25. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/setup.py +0 -0
  26. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/simple_main.py +0 -0
  27. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/tui/__init__.py +0 -0
  28. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/tui/settings/mcp_screen.py +0 -0
  29. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/tui/settings/store.py +0 -0
  30. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/tui/status.py +0 -0
  31. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/tui/tui.py +0 -0
  32. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/tui/utils.py +0 -0
  33. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/user_actions/__init__.py +0 -0
  34. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/user_actions/agent_action.py +0 -0
  35. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/user_actions/exit_session.py +0 -0
  36. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/user_actions/settings_action.py +0 -0
  37. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/user_actions/types.py +0 -0
  38. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/user_actions/utils.py +0 -0
  39. {openhands-1.0.4 → openhands-1.0.5}/openhands_cli/utils.py +0 -0
  40. {openhands-1.0.4 → openhands-1.0.5}/tests/__init__.py +0 -0
  41. {openhands-1.0.4 → openhands-1.0.5}/tests/commands/test_confirm_command.py +0 -0
  42. {openhands-1.0.4 → openhands-1.0.5}/tests/commands/test_new_command.py +0 -0
  43. {openhands-1.0.4 → openhands-1.0.5}/tests/commands/test_resume_command.py +0 -0
  44. {openhands-1.0.4 → openhands-1.0.5}/tests/commands/test_status_command.py +0 -0
  45. {openhands-1.0.4 → openhands-1.0.5}/tests/conftest.py +0 -0
  46. {openhands-1.0.4 → openhands-1.0.5}/tests/settings/test_api_key_preservation.py +0 -0
  47. {openhands-1.0.4 → openhands-1.0.5}/tests/settings/test_default_agent_security_analyzer.py +0 -0
  48. {openhands-1.0.4 → openhands-1.0.5}/tests/settings/test_first_time_user_settings.py +0 -0
  49. {openhands-1.0.4 → openhands-1.0.5}/tests/settings/test_settings_input.py +0 -0
  50. {openhands-1.0.4 → openhands-1.0.5}/tests/test_confirmation_mode.py +0 -0
  51. {openhands-1.0.4 → openhands-1.0.5}/tests/test_conversation_runner.py +0 -0
  52. {openhands-1.0.4 → openhands-1.0.5}/tests/test_directory_separation.py +0 -0
  53. {openhands-1.0.4 → openhands-1.0.5}/tests/test_exit_session_confirmation.py +0 -0
  54. {openhands-1.0.4 → openhands-1.0.5}/tests/test_gui_launcher.py +0 -0
  55. {openhands-1.0.4 → openhands-1.0.5}/tests/test_loading.py +0 -0
  56. {openhands-1.0.4 → openhands-1.0.5}/tests/test_main.py +0 -0
  57. {openhands-1.0.4 → openhands-1.0.5}/tests/test_mcp_config_validation.py +0 -0
  58. {openhands-1.0.4 → openhands-1.0.5}/tests/test_pause_listener.py +0 -0
  59. {openhands-1.0.4 → openhands-1.0.5}/tests/test_session_prompter.py +0 -0
  60. {openhands-1.0.4 → openhands-1.0.5}/tests/test_tui.py +0 -0
  61. {openhands-1.0.4 → openhands-1.0.5}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands
3
- Version: 1.0.4
3
+ Version: 1.0.5
4
4
  Summary: OpenHands CLI - Terminal User Interface for OpenHands AI Agent
5
5
  Author-email: OpenHands Team <contact@all-hands.dev>
6
6
  License: MIT
@@ -20,15 +20,6 @@ from openhands_cli.locations import AGENT_SETTINGS_PATH, PERSISTENCE_DIR
20
20
 
21
21
  from openhands.sdk import LLM
22
22
 
23
- dummy_agent = get_default_cli_agent(
24
- llm=LLM(
25
- model='dummy-model',
26
- api_key='dummy-key',
27
- metadata=get_llm_metadata(model_name='dummy-model', llm_type='openhands'),
28
- ),
29
- cli_mode=True,
30
- )
31
-
32
23
  # =================================================
33
24
  # SECTION: Build Binary
34
25
  # =================================================
@@ -126,7 +117,7 @@ def _is_welcome(line: str) -> bool:
126
117
  return any(marker in s for marker in WELCOME_MARKERS)
127
118
 
128
119
 
129
- def test_executable() -> bool:
120
+ def test_executable(dummy_agent) -> bool:
130
121
  """Test the built executable, measuring boot time and total test time."""
131
122
  print('🧪 Testing the built executable...')
132
123
 
@@ -274,7 +265,14 @@ def main() -> int:
274
265
 
275
266
  # Test the executable
276
267
  if not args.no_test:
277
- if not test_executable():
268
+ dummy_agent = get_default_cli_agent(
269
+ llm=LLM(
270
+ model='dummy-model',
271
+ api_key='dummy-key',
272
+ metadata=get_llm_metadata(model_name='dummy-model', llm_type='openhands'),
273
+ )
274
+ )
275
+ if not test_executable(dummy_agent):
278
276
  print('❌ Executable test failed, build process failed')
279
277
  return 1
280
278
 
@@ -285,4 +283,10 @@ def main() -> int:
285
283
 
286
284
 
287
285
  if __name__ == '__main__':
288
- sys.exit(main())
286
+ try:
287
+ sys.exit(main())
288
+ except Exception as e:
289
+ print(e)
290
+ print('❌ Executable test failed')
291
+ sys.exit(1)
292
+
@@ -127,7 +127,7 @@ def run_cli_entry(resume_conversation_id: str | None = None) -> None:
127
127
  break
128
128
 
129
129
  elif command == '/settings':
130
- settings_screen = SettingsScreen(conversation)
130
+ settings_screen = SettingsScreen(runner.conversation if runner else None)
131
131
  settings_screen.display_settings()
132
132
  continue
133
133
 
@@ -1,6 +1,6 @@
1
1
  import os
2
2
 
3
- from openhands.sdk import LLM, BaseConversation, LocalFileStore
3
+ from openhands.sdk import LLM, BaseConversation, LLMSummarizingCondenser, LocalFileStore
4
4
  from prompt_toolkit import HTML, print_formatted_text
5
5
  from prompt_toolkit.shortcuts import print_container
6
6
  from prompt_toolkit.widgets import Frame, TextArea
@@ -33,9 +33,6 @@ class SettingsScreen:
33
33
  agent_spec = self.agent_store.load()
34
34
  if not agent_spec:
35
35
  return
36
- assert self.conversation is not None, (
37
- 'Conversation must be set to display settings.'
38
- )
39
36
 
40
37
  llm = agent_spec.llm
41
38
  advanced_llm_settings = True if llm.base_url else False
@@ -62,12 +59,20 @@ class SettingsScreen:
62
59
  labels_and_values.extend(
63
60
  [
64
61
  (' API Key', '********' if llm.api_key else 'Not Set'),
62
+ ]
63
+ )
64
+
65
+ if self.conversation:
66
+ labels_and_values.extend([
65
67
  (
66
68
  ' Confirmation Mode',
67
69
  'Enabled'
68
70
  if self.conversation.is_confirmation_mode_active
69
71
  else 'Disabled',
70
- ),
72
+ )
73
+ ])
74
+
75
+ labels_and_values.extend([
71
76
  (
72
77
  ' Memory Condensation',
73
78
  'Enabled' if agent_spec.condenser else 'Disabled',
@@ -153,7 +158,7 @@ class SettingsScreen:
153
158
  api_key = prompt_api_key(
154
159
  step_counter,
155
160
  custom_model.split('/')[0] if len(custom_model.split('/')) > 1 else '',
156
- self.conversation.agent.llm.api_key if self.conversation else None,
161
+ self.conversation.state.agent.llm.api_key if self.conversation else None,
157
162
  escapable=escapable,
158
163
  )
159
164
  memory_condensation = choose_memory_condensation(step_counter)
@@ -182,7 +187,14 @@ class SettingsScreen:
182
187
  if not agent:
183
188
  agent = get_default_cli_agent(llm=llm)
184
189
 
190
+ # Must update all LLMs
185
191
  agent = agent.model_copy(update={'llm': llm})
192
+ condenser = LLMSummarizingCondenser(
193
+ llm=llm.model_copy(
194
+ update={"usage_id": "condenser"}
195
+ )
196
+ )
197
+ agent = agent.model_copy(update={'condenser': condenser})
186
198
  self.agent_store.save(agent)
187
199
 
188
200
  def _save_advanced_settings(
@@ -4,7 +4,7 @@ requires = [ "hatchling>=1.25" ]
4
4
 
5
5
  [project]
6
6
  name = "openhands"
7
- version = "1.0.4"
7
+ version = "1.0.5"
8
8
  description = "OpenHands CLI - Terminal User Interface for OpenHands AI Agent"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -0,0 +1,57 @@
1
+ """Test for the /settings command functionality."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+ from prompt_toolkit.input.defaults import create_pipe_input
5
+ from prompt_toolkit.output.defaults import DummyOutput
6
+
7
+ from openhands_cli.agent_chat import run_cli_entry
8
+ from openhands_cli.user_actions import UserConfirmation
9
+
10
+
11
+ @patch('openhands_cli.agent_chat.exit_session_confirmation')
12
+ @patch('openhands_cli.agent_chat.get_session_prompter')
13
+ @patch('openhands_cli.agent_chat.setup_conversation')
14
+ @patch('openhands_cli.agent_chat.verify_agent_exists_or_setup_agent')
15
+ @patch('openhands_cli.agent_chat.ConversationRunner')
16
+ @patch('openhands_cli.agent_chat.SettingsScreen')
17
+ def test_settings_command_works_without_conversation(
18
+ mock_settings_screen_class,
19
+ mock_runner_cls,
20
+ mock_verify_agent,
21
+ mock_setup_conversation,
22
+ mock_get_session_prompter,
23
+ mock_exit_confirm,
24
+ ):
25
+ """Test that /settings command works when no conversation is active (bug fix scenario)."""
26
+ # Auto-accept the exit prompt to avoid interactive UI
27
+ mock_exit_confirm.return_value = UserConfirmation.ACCEPT
28
+
29
+ # Mock agent verification to succeed
30
+ mock_agent = MagicMock()
31
+ mock_verify_agent.return_value = mock_agent
32
+
33
+ # Mock the SettingsScreen instance
34
+ mock_settings_screen = MagicMock()
35
+ mock_settings_screen_class.return_value = mock_settings_screen
36
+
37
+ # No runner initially (simulates starting CLI without a conversation)
38
+ mock_runner_cls.return_value = None
39
+
40
+ # Real session fed by a pipe
41
+ from openhands_cli.user_actions.utils import get_session_prompter as real_get_session_prompter
42
+ with create_pipe_input() as pipe:
43
+ output = DummyOutput()
44
+ session = real_get_session_prompter(input=pipe, output=output)
45
+ mock_get_session_prompter.return_value = session
46
+
47
+ # Trigger /settings, then /exit (exit will be auto-accepted)
48
+ for ch in "/settings\r/exit\r":
49
+ pipe.send_text(ch)
50
+
51
+ run_cli_entry(None)
52
+
53
+ # Assert SettingsScreen was created with None conversation (the bug fix)
54
+ mock_settings_screen_class.assert_called_once_with(None)
55
+
56
+ # Assert display_settings was called (settings screen was shown)
57
+ mock_settings_screen.display_settings.assert_called_once()
@@ -121,6 +121,38 @@ def test_update_existing_settings_workflow(tmp_path: Path):
121
121
  assert True # If we get here, the workflow completed successfully
122
122
 
123
123
 
124
+ def test_all_llms_in_agent_are_updated():
125
+ """Test that modifying LLM settings creates multiple LLMs with same API key but different usage_ids."""
126
+ # Create a screen with existing agent settings
127
+ screen = SettingsScreen(conversation=None)
128
+ initial_llm = LLM(model='openai/gpt-3.5-turbo', api_key=SecretStr('sk-initial'), usage_id='test-service')
129
+ initial_agent = get_default_cli_agent(llm=initial_llm)
130
+
131
+ # Mock the agent store to return the initial agent and capture the save call
132
+ with (
133
+ patch.object(screen.agent_store, 'load', return_value=initial_agent),
134
+ patch.object(screen.agent_store, 'save') as mock_save
135
+ ):
136
+ # Modify the LLM settings with new API key
137
+ screen._save_llm_settings(model='openai/gpt-4o-mini', api_key='sk-updated-123')
138
+ mock_save.assert_called_once()
139
+
140
+ # Get the saved agent from the mock
141
+ saved_agent = mock_save.call_args[0][0]
142
+ all_llms = list(saved_agent.get_all_llms())
143
+ assert len(all_llms) >= 2, f"Expected at least 2 LLMs, got {len(all_llms)}"
144
+
145
+ # Verify all LLMs have the same API key
146
+ api_keys = [llm.api_key.get_secret_value() for llm in all_llms]
147
+ assert all(api_key == 'sk-updated-123' for api_key in api_keys), \
148
+ f"Not all LLMs have the same API key: {api_keys}"
149
+
150
+ # Verify none of the usage_id attributes match
151
+ usage_ids = [llm.usage_id for llm in all_llms]
152
+ assert len(set(usage_ids)) == len(usage_ids), \
153
+ f"Some usage_ids are duplicated: {usage_ids}"
154
+
155
+
124
156
  @pytest.mark.parametrize(
125
157
  'step_to_cancel',
126
158
  ['type', 'provider', 'model', 'apikey', 'save'],
@@ -1828,7 +1828,7 @@ wheels = [
1828
1828
 
1829
1829
  [[package]]
1830
1830
  name = "openhands"
1831
- version = "1.0.3"
1831
+ version = "1.0.5"
1832
1832
  source = { editable = "." }
1833
1833
  dependencies = [
1834
1834
  { name = "openhands-sdk" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes