Skip to content

Commit 44ab87c

Browse files
Update agent-sdk version and import paths (#26)
* Update agent-sdk version and import paths - Update agent-sdk SHA to latest commit (ab8980714dd397f26fe811227afbc533c59fae70) - Change subdirectory from openhands/core to openhands/sdk - Update import paths from openhands.core.* to openhands.sdk.* - Replace CodeActAgent with Agent class - Update lock file with new dependencies - All tests passing Fixes #25 Co-authored-by: openhands <openhands@all-hands.dev> * Fix binary build error and improve error handling - Update PyInstaller spec to use new agent-sdk import paths (openhands.sdk instead of openhands.core) - Fix missing prompt file error by updating data collection paths - Improve error handling to return proper exit codes instead of exceptions - Update CI to properly test binary startup and catch errors - Add comprehensive binary testing for missing API keys and prompt files - Update tests to match new return code behavior Fixes the 'Prompt file system_prompt.j2 not found' error in binary builds. * Refactor setup_agent to raise exceptions instead of returning exit codes - Created AgentSetupError exception class with exit_code attribute - Modified setup_agent() to raise AgentSetupError instead of returning exit codes - Updated run_agent_chat() to catch AgentSetupError and return appropriate exit code - This allows GitHub CI to properly catch and handle setup failures Co-authored-by: openhands <openhands@all-hands.dev> * Rework agent chat and CI to use message outputs instead of exit codes - Remove exit code dependencies from agent_chat.py and simple_main.py - Functions now raise exceptions and exit normally with sys.exit() - Update CI to test message outputs for long-lived process behavior - Add comprehensive CI tests for interactive commands (/help, /status, /exit) - Update unit tests to expect SystemExit exceptions instead of return codes - Improve error handling to be more natural and maintainable Co-authored-by: openhands <openhands@all-hands.dev> * Simplify CI to use single test job with environment variables - Remove complex error checking and multiple test jobs - Use single test job with LITELLM_API_KEY and LITELLM_MODEL already exported - Test /status, /help, and /exit commands in sequence - Simply display output without explicit validation checks - Cleaner and more maintainable CI approach Co-authored-by: openhands <openhands@all-hands.dev> * Simplify build and CI tests - Focus CI on core functionality: app startup and /help command - Remove complex exit code testing and error handling verification - Simplify build.py test_executable() to test /help command output - Update CI workflow to verify expected help output content - Maintain all existing functionality while reducing test complexity Co-authored-by: openhands <openhands@all-hands.dev> * Clean up error handling: remove sys.exit() calls and let application finish normally - Remove sys.exit() calls from agent_chat.py and simple_main.py - Replace with proper exception raising to let application finish normally - CI and build will still catch errors through output validation - Tested that CI still detects build/runtime errors without explicit exit codes Co-authored-by: openhands <openhands@all-hands.dev> * Fix linting: remove unused sys import Co-authored-by: openhands <openhands@all-hands.dev> * Update tests to match new error handling behavior - Tests now expect original exceptions instead of SystemExit - ImportError and general exceptions are now raised directly - This aligns with the cleanup that removed sys.exit() calls Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev>
1 parent b539609 commit 44ab87c

File tree

8 files changed

+151
-76
lines changed

8 files changed

+151
-76
lines changed

.github/workflows/build-test.yml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,39 @@ jobs:
3838
run: |
3939
uv sync --dev
4040
41-
- name: Build and test binary executable
41+
- name: Build binary executable
42+
run: |
43+
./build.sh --install-pyinstaller --no-test
44+
45+
- name: Test binary startup and /help command
4246
env:
4347
LITELLM_API_KEY: dummy-ci-key
4448
LITELLM_MODEL: dummy-ci-model
4549
run: |
46-
./build.sh --install-pyinstaller
50+
# Test that binary starts and responds to /help command
51+
echo "Testing binary startup and /help command..."
52+
53+
# Send /help command and then exit
54+
echo -e "/help\n/exit" | timeout 30s ./dist/openhands-cli 2>&1 | tee output.log || true
55+
56+
# Check that the application started successfully
57+
if grep -q "OpenHands CLI Help" output.log; then
58+
echo "✅ Application started and /help command works correctly"
59+
else
60+
echo "❌ /help command output not found"
61+
echo "Full output:"
62+
cat output.log
63+
exit 1
64+
fi
65+
66+
# Check for expected help content
67+
if grep -q "Available commands:" output.log && grep -q "/exit - Exit the application" output.log; then
68+
echo "✅ Help content is correct"
69+
else
70+
echo "❌ Expected help content not found"
71+
echo "Full output:"
72+
cat output.log
73+
exit 1
74+
fi
75+
76+
echo "✅ Binary test completed successfully"

build.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def build_executable(
9696

9797

9898
def test_executable() -> bool:
99-
"""Test the built executable."""
99+
"""Test the built executable with simplified checks."""
100100
print("🧪 Testing the built executable...")
101101

102102
exe_path = Path("dist/openhands-cli")
@@ -112,30 +112,34 @@ def test_executable() -> bool:
112112
if os.name != "nt":
113113
os.chmod(exe_path, 0o755)
114114

115-
# Run the executable with a timeout
115+
# Simple test: Check that executable can start and respond to /help command
116+
print(" Testing executable startup and /help command...")
116117
result = subprocess.run(
117-
[str(exe_path)], capture_output=True, text=True, timeout=30
118+
[str(exe_path)],
119+
capture_output=True,
120+
text=True,
121+
timeout=15,
122+
input="/help\n/exit\n", # Send /help command then exit
123+
env={
124+
**os.environ,
125+
"LITELLM_API_KEY": "dummy-test-key",
126+
"LITELLM_MODEL": "dummy-model",
127+
},
118128
)
119129

120-
if result.returncode == 0:
121-
print("✅ Executable test passed!")
122-
print("Output preview:")
123-
print(
124-
result.stdout[:500] + "..."
125-
if len(result.stdout) > 500
126-
else result.stdout
127-
)
130+
# Check for expected help output
131+
output = result.stdout + result.stderr
132+
if "OpenHands CLI Help" in output and "Available commands:" in output:
133+
print(" ✅ Executable starts and /help command works correctly")
128134
return True
129135
else:
130-
print(f"❌ Executable test failed with return code {result.returncode}")
131-
print("STDERR:", result.stderr)
136+
print(" ❌ Expected help output not found")
137+
print(" Combined output:", output[:1000])
132138
return False
133139

134140
except subprocess.TimeoutExpired:
135-
print(
136-
"⚠️ Executable test timed out (this might be normal for interactive CLIs)"
137-
)
138-
return True
141+
print(" ❌ Executable test timed out")
142+
return False
139143
except Exception as e:
140144
print(f"❌ Error testing executable: {e}")
141145
return False

openhands-cli.spec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ a = Analysis(
3434
*collect_data_files('tiktoken_ext'),
3535
*collect_data_files('litellm'),
3636
# Include Jinja prompt templates required by the agent SDK
37-
*collect_data_files('openhands.core.agent.codeact_agent', includes=['prompts/*.j2']),
37+
*collect_data_files('openhands.sdk.agent.agent', includes=['prompts/*.j2']),
3838
],
3939
hiddenimports=[
4040
# Explicitly include modules that might not be detected automatically
4141
'openhands_cli.tui',
4242
'openhands_cli.pt_style',
4343
*collect_submodules('prompt_toolkit'),
4444
# Include OpenHands SDK submodules explicitly to avoid resolution issues
45-
*collect_submodules('openhands.core'),
45+
*collect_submodules('openhands.sdk'),
4646
*collect_submodules('openhands.tools'),
4747

4848
*collect_submodules('tiktoken'),

openhands_cli/agent_chat.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,48 @@
2222
from openhands_cli.tui import CommandCompleter, display_banner, display_help
2323

2424
try:
25-
from openhands.core.agent.codeact_agent import CodeActAgent
26-
from openhands.core.config import LLMConfig
27-
from openhands.core.conversation import Conversation
28-
from openhands.core.event import EventType
29-
from openhands.core.llm import LLM, Message, TextContent
30-
from openhands.core.tool import Tool
31-
from openhands.tools.execute_bash import BashExecutor, execute_bash_tool
32-
from openhands.tools.str_replace_editor import (
25+
from openhands.sdk import (
26+
LLM,
27+
Agent,
28+
Conversation,
29+
EventType,
30+
LLMConfig,
31+
Message,
32+
TextContent,
33+
Tool,
34+
)
35+
from openhands.tools import (
36+
BashExecutor,
3337
FileEditorExecutor,
38+
execute_bash_tool,
3439
str_replace_editor_tool,
3540
)
3641
except ImportError as e:
3742
print_formatted_text(HTML(f"<red>Error importing OpenHands SDK: {e}</red>"))
3843
print_formatted_text(
3944
HTML("<yellow>Please ensure the openhands-sdk is properly installed.</yellow>")
4045
)
41-
sys.exit(1)
46+
raise
4247

4348

4449
logger = logging.getLogger(__name__)
4550

4651

47-
def setup_agent() -> tuple[LLM | None, CodeActAgent | None, Conversation | None]:
48-
"""Setup the agent with environment variables."""
52+
class AgentSetupError(Exception):
53+
"""Exception raised when agent setup fails."""
54+
55+
pass
56+
57+
58+
def setup_agent() -> tuple[LLM, Agent, Conversation]:
59+
"""Setup the agent with environment variables.
60+
61+
Returns:
62+
tuple: (llm, agent, conversation)
63+
64+
Raises:
65+
AgentSetupError: If agent setup fails
66+
"""
4967
try:
5068
# Get API configuration from environment
5169
api_key = os.getenv("LITELLM_API_KEY") or os.getenv("OPENAI_API_KEY")
@@ -58,7 +76,9 @@ def setup_agent() -> tuple[LLM | None, CodeActAgent | None, Conversation | None]
5876
"<red>Error: No API key found. Please set LITELLM_API_KEY or OPENAI_API_KEY environment variable.</red>"
5977
)
6078
)
61-
return None, None, None
79+
raise AgentSetupError(
80+
"No API key found. Please set LITELLM_API_KEY or OPENAI_API_KEY environment variable."
81+
)
6282

6383
# Configure LLM
6484
llm_config = LLMConfig(
@@ -81,7 +101,7 @@ def setup_agent() -> tuple[LLM | None, CodeActAgent | None, Conversation | None]
81101
]
82102

83103
# Create agent
84-
agent = CodeActAgent(llm=llm, tools=tools)
104+
agent = Agent(llm=llm, tools=tools)
85105

86106
# Setup conversation with callback
87107
def conversation_callback(event: EventType) -> None:
@@ -94,10 +114,13 @@ def conversation_callback(event: EventType) -> None:
94114
)
95115
return llm, agent, conversation
96116

117+
except AgentSetupError:
118+
# Re-raise AgentSetupError as-is
119+
raise
97120
except Exception as e:
98121
print_formatted_text(HTML(f"<red>Error setting up agent: {str(e)}</red>"))
99122
traceback.print_exc()
100-
return None, None, None
123+
raise AgentSetupError(f"Error setting up agent: {str(e)}") from e
101124

102125

103126
def display_welcome(session_id: str = "chat") -> None:
@@ -114,11 +137,15 @@ def display_welcome(session_id: str = "chat") -> None:
114137

115138

116139
def run_agent_chat() -> None:
117-
"""Run the agent chat session using the agent SDK."""
118-
# Setup agent
140+
"""Run the agent chat session using the agent SDK.
141+
142+
Raises:
143+
AgentSetupError: If agent setup fails
144+
KeyboardInterrupt: If user interrupts the session
145+
EOFError: If EOF is encountered
146+
"""
147+
# Setup agent - let exceptions bubble up
119148
llm, agent, conversation = setup_agent()
120-
if not agent or not conversation:
121-
return
122149

123150
# Generate session ID
124151
import uuid
@@ -200,14 +227,24 @@ def run_agent_chat() -> None:
200227

201228

202229
def main() -> None:
203-
"""Main entry point for agent chat."""
230+
"""Main entry point for agent chat.
231+
232+
Raises:
233+
AgentSetupError: If agent setup fails
234+
Exception: On unexpected errors
235+
"""
204236
try:
205237
run_agent_chat()
206238
except KeyboardInterrupt:
207239
print_formatted_text(HTML("\n<yellow>Goodbye! 👋</yellow>"))
240+
except AgentSetupError as e:
241+
# Agent setup errors are already printed in setup_agent()
242+
logger.error(f"Agent setup failed: {e}")
243+
raise
208244
except Exception as e:
209245
print_formatted_text(HTML(f"<red>Unexpected error: {str(e)}</red>"))
210246
logger.error(f"Main error: {e}")
247+
raise
211248

212249

213250
if __name__ == "__main__":

openhands_cli/simple_main.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
This is a simplified version that demonstrates the TUI functionality.
55
"""
66

7-
import sys
87
import traceback
98

109
from prompt_toolkit import PromptSession, print_formatted_text
@@ -58,14 +57,18 @@ def show_tui_demo() -> None:
5857
print("TUI demo complete! 🎉")
5958

6059

61-
def main() -> int:
62-
"""Main entry point for the OpenHands CLI."""
60+
def main() -> None:
61+
"""Main entry point for the OpenHands CLI.
62+
63+
Raises:
64+
ImportError: If agent chat dependencies are missing
65+
Exception: On other error conditions
66+
"""
6367
try:
6468
# Start agent chat directly by default
6569
from openhands_cli.agent_chat import main as run_agent_chat
6670

6771
run_agent_chat()
68-
return 0
6972

7073
except ImportError as e:
7174
print_formatted_text(
@@ -74,18 +77,16 @@ def main() -> int:
7477
print_formatted_text(
7578
HTML("<yellow>Please ensure the agent SDK is properly installed.</yellow>")
7679
)
77-
return 1
80+
raise
7881
except KeyboardInterrupt:
7982
print_formatted_text(HTML("\n<yellow>Goodbye! 👋</yellow>"))
80-
return 0
8183
except EOFError:
8284
print_formatted_text(HTML("\n<yellow>Goodbye! 👋</yellow>"))
83-
return 0
8485
except Exception as e:
8586
print_formatted_text(HTML(f"<red>Error starting agent chat: {e}</red>"))
8687
traceback.print_exc()
87-
return 1
88+
raise
8889

8990

9091
if __name__ == "__main__":
91-
sys.exit(main())
92+
main()

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ classifiers = [
1717
"Programming Language :: Python :: 3.13",
1818
]
1919
dependencies = [
20-
"openhands-sdk @ git+https://github.com/All-Hands-AI/agent-sdk.git@288e440b344c67c66e2093215521a1394b1509b6#subdirectory=openhands/core",
21-
"openhands-tools @ git+https://github.com/All-Hands-AI/agent-sdk.git@288e440b344c67c66e2093215521a1394b1509b6#subdirectory=openhands/tools",
20+
"openhands-sdk @ git+https://github.com/All-Hands-AI/agent-sdk.git@ab8980714dd397f26fe811227afbc533c59fae70#subdirectory=openhands/sdk",
21+
"openhands-tools @ git+https://github.com/All-Hands-AI/agent-sdk.git@ab8980714dd397f26fe811227afbc533c59fae70#subdirectory=openhands/tools",
2222
"prompt-toolkit>=3",
2323
]
2424

@@ -37,6 +37,7 @@ scripts.openhands-cli = "openhands_cli.simple_main:main"
3737
dev = [
3838
"pre-commit>=4.3",
3939
"pyinstaller>=6.15",
40+
"pytest>=8.4.1",
4041
]
4142

4243
[tool.poetry]
@@ -51,8 +52,8 @@ packages = [ { include = "openhands_cli" } ]
5152
[tool.poetry.dependencies]
5253
python = "^3.12"
5354
prompt-toolkit = "^3.0.0"
54-
openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", rev = "288e440b344c67c66e2093215521a1394b1509b6", subdirectory = "openhands/core" }
55-
openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", rev = "288e440b344c67c66e2093215521a1394b1509b6", subdirectory = "openhands/tools" }
55+
openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", rev = "ab8980714dd397f26fe811227afbc533c59fae70", subdirectory = "openhands/sdk" }
56+
openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", rev = "ab8980714dd397f26fe811227afbc533c59fae70", subdirectory = "openhands/tools" }
5657

5758
[tool.poetry.group.dev.dependencies]
5859
pytest = "^7.0.0"

0 commit comments

Comments
 (0)