Skip to content

Make CLI pip-installable #8772

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/integration-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
Hi! I started running the integration tests on your PR. You will receive a comment with the results shortly.

- name: Install Python dependencies using Poetry
run: poetry install --without evaluation
run: poetry install --with dev,test,runtime

- name: Configure config.toml for testing with Haiku
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/py-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
- name: Install Python dependencies using Poetry
run: poetry install --without evaluation
run: poetry install --with dev,test,runtime
- name: Build Environment
run: make build
- name: Run Unit Tests
Expand All @@ -71,7 +71,7 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
- name: Install Python dependencies using Poetry
run: poetry install --without evaluation
run: poetry install --with dev,test,runtime
- name: Run Windows unit tests
run: poetry run pytest -svv tests/unit/test_windows_bash.py
- name: Run Windows runtime tests with LocalRuntime
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ install-python-dependencies:
echo "Installing only POETRY_GROUP=${POETRY_GROUP}"; \
poetry install --only $${POETRY_GROUP}; \
else \
poetry install; \
poetry install --with dev,test; \
fi
@if [ "${INSTALL_PLAYWRIGHT}" != "false" ] && [ "${INSTALL_PLAYWRIGHT}" != "0" ]; then \
if [ -f "/etc/manjaro-release" ]; then \
Expand Down
2 changes: 1 addition & 1 deletion containers/app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ RUN apt-get update -y \

COPY ./pyproject.toml ./poetry.lock ./
RUN touch README.md
RUN export POETRY_CACHE_DIR && poetry install --without evaluation --no-root && rm -rf $POETRY_CACHE_DIR
RUN export POETRY_CACHE_DIR && poetry install --no-root && rm -rf $POETRY_CACHE_DIR

FROM python:3.12.3-slim AS openhands-app

Expand Down
6 changes: 3 additions & 3 deletions openhands/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ def on_event(event: Event) -> None:
return new_session_requested


async def main(loop: asyncio.AbstractEventLoop) -> None:
async def main_with_loop(loop: asyncio.AbstractEventLoop) -> None:
"""Runs the agent in CLI mode."""
args = parse_arguments()

Expand Down Expand Up @@ -419,11 +419,11 @@ async def main(loop: asyncio.AbstractEventLoop) -> None:
)


if __name__ == '__main__':
def main():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(main(loop))
loop.run_until_complete(main_with_loop(loop))
except KeyboardInterrupt:
print('Received keyboard interrupt, shutting down...')
except ConnectionRefusedError as e:
Expand Down
63 changes: 5 additions & 58 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 45 additions & 31 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,78 +20,86 @@ packages = [

[tool.poetry.dependencies]
python = "^3.12,<3.14"
litellm = "^1.60.0, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
aiohttp = ">=3.9.0,!=3.11.13" # Pin to avoid yanked version 3.11.13
google-generativeai = "*" # To use litellm with Gemini Pro API
google-api-python-client = "^2.164.0" # For Google Sheets API
google-auth-httplib2 = "*" # For Google Sheets authentication
google-auth-oauthlib = "*" # For Google Sheets OAuth
litellm = "^1.60.0, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
aiohttp = ">=3.9.0,!=3.11.13" # Pin to avoid yanked version 3.11.13
termcolor = "*"
docker = "*"
fastapi = "*"
toml = "*"
uvicorn = "*"
types-toml = "*"
uvicorn = "*"
numpy = "*"
json-repair = "*"
browsergym-core = "0.13.3" # integrate browsergym-core as the browsing interface
html2text = "*"
e2b = ">=1.0.5,<1.4.0"
pexpect = "*"
jinja2 = "^3.1.3"
python-multipart = "*"
boto3 = "*"
minio = "^7.2.8"
tenacity = ">=8.5,<10.0"
zope-interface = "7.2"
pathspec = "^0.12.1"
google-cloud-aiplatform = "*"
anthropic = { extras = [ "vertex" ], version = "*" }
tree-sitter = "^0.24.0"
bashlex = "^0.18"
pyjwt = "^2.9.0"
dirhash = "*"
python-frontmatter = "^1.1.0"
python-docx = "*"
PyPDF2 = "*"
python-pptx = "*"
pylatexenc = "*"
tornado = "*"
python-dotenv = "*"
pylcs = "^0.1.1"
whatthepatch = "^1.0.6"
protobuf = "^4.21.6,<5.0.0" # chromadb currently fails on 5.0+
protobuf = "^4.21.6,<5.0.0" # chromadb currently fails on 5.0+
opentelemetry-api = "1.25.0"
opentelemetry-exporter-otlp-proto-grpc = "1.25.0"
modal = ">=0.66.26,<0.78.0"
runloop-api-client = "0.33.0"
libtmux = ">=0.37,<0.40"
pygithub = "^2.5.0"
joblib = "*"
openhands-aci = "0.2.14"
python-socketio = "^5.11.4"
redis = ">=5.2,<7.0"
sse-starlette = "^2.1.3"
psutil = "*"
stripe = ">=11.5,<13.0"
ipywidgets = "^8.1.5"
qtconsole = "^5.6.1"
memory-profiler = "^0.61.0"
daytona-sdk = "0.18.1"
python-json-logger = "^3.2.1"
prompt-toolkit = "^3.0.50"
poetry = "^2.1.2"
anyio = "4.9.0"
pythonnet = "*"
fastmcp = "^2.3.3"
mcpm = "1.12.0"
python-frontmatter = "^1.1.0"
browsergym-core = "0.13.3" # integrate browsergym-core as the browsing interface

# FIXME: These should no longer be required
json-repair = "*"

# TODO: Should these go into the runtime group?
ipywidgets = "^8.1.5"
qtconsole = "^5.6.1"
PyPDF2 = "*"
python-pptx = "*"
pylatexenc = "*"
python-docx = "*"
html2text = "*"
libtmux = ">=0.37,<0.40"

# TODO: These are integrations that should probably be optional
redis = ">=5.2,<7.0"
minio = "^7.2.8"
daytona-sdk = "0.18.1"
stripe = ">=11.5,<13.0"
modal = ">=0.66.26,<0.78.0"
google-cloud-aiplatform = "*"
anthropic = { extras = [ "vertex" ], version = "*" }
google-generativeai = "*" # To use litellm with Gemini Pro API
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right to drop these here with a TODO, they're like three different categories of things and may need some thought

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I tried to get cute with making more of these optional, but it was way too hard to figure out exactly what was truly optional. It's a painful dev loop

e2b = ">=1.0.5,<1.4.0"
google-api-python-client = "^2.164.0"
runloop-api-client = "0.33.0"

[tool.poetry.group.dev]
optional = true

[tool.poetry.group.dev.dependencies]
ruff = "0.11.11"
mypy = "1.15.0"
pre-commit = "4.2.0"
build = "*"
types-setuptools = "*"
memory-profiler = "^0.61.0"

[tool.poetry.group.test]
optional = true

[tool.poetry.group.test.dependencies]
pytest = "*"
Expand All @@ -104,12 +112,18 @@ pandas = "*"
reportlab = "*"
gevent = ">=24.2.1,<26.0.0"

[tool.poetry.group.runtime]
optional = true

[tool.poetry.group.runtime.dependencies]
jupyterlab = "*"
notebook = "*"
jupyter_kernel_gateway = "*"
flake8 = "*"

[tool.poetry.group.evaluation]
optional = true

[tool.poetry.group.evaluation.dependencies]
streamlit = "*"
whatthepatch = "*"
Expand Down
10 changes: 5 additions & 5 deletions tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ async def test_main_without_task(
mock_run_session.return_value = False

# Run the function
await cli.main(loop)
await cli.main_with_loop(loop)

# Assertions
mock_parse_args.assert_called_once()
Expand Down Expand Up @@ -458,7 +458,7 @@ async def test_main_with_task(
mock_run_session.side_effect = [True, False]

# Run the function
await cli.main(loop)
await cli.main_with_loop(loop)

# Assertions
mock_parse_args.assert_called_once()
Expand Down Expand Up @@ -553,7 +553,7 @@ async def test_main_with_session_name_passes_name_to_run_session(
mock_run_session.return_value = False

# Run the function
await cli.main(loop)
await cli.main_with_loop(loop)

# Assertions
mock_parse_args.assert_called_once()
Expand Down Expand Up @@ -713,7 +713,7 @@ async def test_main_security_check_fails(
mock_check_security.return_value = False

# Run the function
await cli.main(loop)
await cli.main_with_loop(loop)

# Assertions
mock_parse_args.assert_called_once()
Expand Down Expand Up @@ -796,7 +796,7 @@ async def test_config_loading_order(
mock_run_session.return_value = False # No new session requested

# Run the function
await cli.main(loop)
await cli.main_with_loop(loop)

# Assertions for argument parsing and config setup
mock_parse_args.assert_called_once()
Expand Down
Loading