From 08df955ba73d41554e2e090c0bac558a1587355f Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Wed, 7 Jan 2026 07:24:27 +0100 Subject: [PATCH] =?UTF-8?q?Clarify=20OpenHands=20naming=20(replace=20?= =?UTF-8?q?=E2=80=9COSS=E2=80=9D=20wording=20in=20docs=20and=20backend)=20?= =?UTF-8?q?(#12235)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: openhands --- .openhands/microagents/repo.md | 6 +++--- dev_config/python/.pre-commit-config.yaml | 9 +++++++++ enterprise/README.md | 20 +++++++++---------- enterprise/enterprise_local/README.md | 16 +++++++-------- frontend/__tests__/MSW.md | 4 ++-- .../app_conversation_router.py | 2 +- openhands/app_server/config.py | 2 +- openhands/app_server/event/event_router.py | 2 +- .../event_callback/webhook_router.py | 4 ++-- .../app_server/sandbox/sandbox_router.py | 2 +- .../app_server/sandbox/sandbox_spec_router.py | 2 +- .../services/db_session_injector.py | 2 +- .../app_server/user/auth_user_context.py | 2 +- openhands/app_server/user/user_router.py | 2 +- .../azure_devops/service/repos.py | 2 +- .../integrations/gitlab/service/repos.py | 2 +- openhands/server/app.py | 2 +- openhands/server/config/server_config.py | 2 +- openhands/server/types.py | 5 ++++- .../bitbucket/test_bitbucket_repos.py | 8 ++++---- tests/unit/server/routes/test_mcp_routes.py | 2 +- .../services/test_conversation_service.py | 6 +++--- tests/unit/test_forgejo_service.py | 4 ++-- 23 files changed, 60 insertions(+), 48 deletions(-) diff --git a/.openhands/microagents/repo.md b/.openhands/microagents/repo.md index cd3ef33074..425ca5a1a6 100644 --- a/.openhands/microagents/repo.md +++ b/.openhands/microagents/repo.md @@ -150,9 +150,9 @@ Each integration follows a consistent pattern with service classes, storage mode **Important Notes:** - Enterprise code is licensed under Polyform Free Trial License (30-day limit) -- The enterprise server extends the OSS server through dynamic imports +- The enterprise server extends the OpenHands server through dynamic imports - Database changes require careful migration planning in `enterprise/migrations/` -- Always test changes in both OSS and enterprise contexts +- Always test changes in both OpenHands and enterprise contexts - Use the enterprise-specific Makefile commands for development **Enterprise Testing Best Practices:** @@ -166,7 +166,7 @@ Each integration follows a consistent pattern with service classes, storage mode **Import Patterns:** - Use relative imports without `enterprise.` prefix in enterprise code - Example: `from storage.database import session_maker` not `from enterprise.storage.database import session_maker` -- This ensures code works in both OSS and enterprise contexts +- This ensures code works in both OpenHands and enterprise contexts **Test Structure:** - Place tests in `enterprise/tests/unit/` following the same structure as the source code diff --git a/dev_config/python/.pre-commit-config.yaml b/dev_config/python/.pre-commit-config.yaml index 2063e60562..bb5eb607b8 100644 --- a/dev_config/python/.pre-commit-config.yaml +++ b/dev_config/python/.pre-commit-config.yaml @@ -10,6 +10,15 @@ repos: args: ["--allow-multiple-documents"] - id: debug-statements + + - repo: local + hooks: + - id: warn-appmode-oss + name: "Warn on AppMode.OSS in backend (use AppMode.OPENHANDS)" + language: system + entry: bash -lc 'if rg -n "\\bAppMode\\.OSS\\b" openhands tests/unit; then echo "Found AppMode.OSS usage. Prefer AppMode.OPENHANDS."; exit 1; fi' + pass_filenames: false + - repo: https://github.com/tox-dev/pyproject-fmt rev: v2.5.1 hooks: diff --git a/enterprise/README.md b/enterprise/README.md index 8be6f3bd8a..be8bb371c7 100644 --- a/enterprise/README.md +++ b/enterprise/README.md @@ -10,13 +10,13 @@ This directory contains the enterprise server used by [OpenHands Cloud](https:// You may also want to check out the MIT-licensed [OpenHands](https://github.com/OpenHands/OpenHands) -## Extension of OpenHands (OSS) +## Extension of OpenHands -The code in `/enterprise` directory builds on top of open source (OSS) code, extending its functionality. The enterprise code is entangled with the OSS code in two ways +The code in `/enterprise` builds on top of OpenHands (MIT-licensed), extending its functionality. The enterprise code is entangled with OpenHands in two ways: -- Enterprise stacks on top of OSS. For example, the middleware in enterprise is stacked right on top of the middlewares in OSS. In `SAAS`, the middleware from BOTH repos will be present and running (which can sometimes cause conflicts) +- Enterprise stacks on top of OpenHands. For example, the middleware in enterprise is stacked right on top of the middlewares in OpenHands. In `SAAS`, the middleware from BOTH repos will be present and running (which can sometimes cause conflicts) -- Enterprise overrides the implementation in OSS (only one is present at a time). For example, the server config SaasServerConfig which overrides [`ServerConfig`](https://github.com/OpenHands/OpenHands/blob/main/openhands/server/config/server_config.py#L8) on OSS. This is done through dynamic imports ([see here](https://github.com/OpenHands/OpenHands/blob/main/openhands/server/config/server_config.py#L37-#L45)) +- Enterprise overrides the implementation in OpenHands (only one is present at a time). For example, the server config SaasServerConfig overrides [`ServerConfig`](https://github.com/OpenHands/OpenHands/blob/main/openhands/server/config/server_config.py#L8) in OpenHands. This is done through dynamic imports ([see here](https://github.com/OpenHands/OpenHands/blob/main/openhands/server/config/server_config.py#L37-#L45)) Key areas that change on `SAAS` are @@ -26,11 +26,11 @@ Key areas that change on `SAAS` are ### Authentication -| Aspect | OSS | Enterprise | +| Aspect | OpenHands | Enterprise | | ------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -| **Authentication Method** | User adds a personal access token (PAT) through the UI | User performs OAuth through the UI. The Github app provides a short-lived access token and refresh token | +| **Authentication Method** | User adds a personal access token (PAT) through the UI | User performs OAuth through the UI. The GitHub app provides a short-lived access token and refresh token | | **Token Storage** | PAT is stored in **Settings** | Token is stored in **GithubTokenManager** (a file store in our backend) | -| **Authenticated status** | We simply check if token exists in `Settings` | We issue a signed cookie with `github_user_id` during oauth, so subsequent requests with the cookie can be considered authenticated | +| **Authenticated status** | We simply check if token exists in `Settings` | We issue a signed cookie with `github_user_id` during OAuth, so subsequent requests with the cookie can be considered authenticated | Note that in the future, authentication will happen via keycloak. All modifications for authentication will happen in enterprise. @@ -38,7 +38,7 @@ Note that in the future, authentication will happen via keycloak. All modificati The github service is responsible for interacting with Github APIs. As a consequence, it uses the user's token and refreshes it if need be -| Aspect | OSS | Enterprise | +| Aspect | OpenHands | Enterprise | | ------------------------- | -------------------------------------- | ---------------------------------------------- | | **Class used** | `GitHubService` | `SaaSGitHubService` | | **Token used** | User's PAT fetched from `Settings` | User's token fetched from `GitHubTokenManager` | @@ -50,7 +50,7 @@ NOTE: in the future we will simply replace the `GithubTokenManager` with keycloa ## User ID vs User Token -- On OSS, the entire APP revolves around the Github token the user sets. `openhands/server` uses `request.state.github_token` for the entire app +- In OpenHands, the entire app revolves around the GitHub token the user sets. `openhands/server` uses `request.state.github_token` for the entire app - On Enterprise, the entire APP resolves around the Github User ID. This is because the cookie sets it, so `openhands/server` AND `enterprise/server` depend on it and completly ignore `request.state.github_token` (token is fetched from `GithubTokenManager` instead) -Note that introducing Github User ID on OSS, for instance, will cause large breakages. +Note that introducing GitHub User ID in OpenHands, for instance, will cause large breakages. diff --git a/enterprise/enterprise_local/README.md b/enterprise/enterprise_local/README.md index 18dee5b144..b57e82d7a9 100644 --- a/enterprise/enterprise_local/README.md +++ b/enterprise/enterprise_local/README.md @@ -2,7 +2,7 @@ You have a few options here, which are expanded on below: -- A simple local development setup, with live reloading for both OSS and this repo +- A simple local development setup, with live reloading for both OpenHands and this repo - A more complex setup that includes Redis - An even more complex setup that includes GitHub events @@ -26,7 +26,7 @@ Before starting, make sure you have the following tools installed: ## Option 1: Simple local development -This option will allow you to modify the both the OSS code and the code in this repo, +This option will allow you to modify both the OpenHands code and the code in this repo, and see the changes in real-time. This option works best for most scenarios. The only thing it's missing is @@ -105,7 +105,7 @@ export REDIS_PORT=6379 (see above) -### 2. Build OSS Openhands +### 2. Build OpenHands Develop on [Openhands](https://github.com/All-Hands-AI/OpenHands) locally. When ready, run the following inside Openhands repo (not the Deploy repo) @@ -155,7 +155,7 @@ Visit the tunnel domain found in Step 4 to run the app (`https://bc71-2603-7000- ### Local Debugging with VSCode -Local Development necessitates running a version of OpenHands that is as similar as possible to the version running in the SAAS Environment. Before running these steps, it is assumed you have a local development version of the OSS OpenHands project running. +Local Development necessitates running a version of OpenHands that is as similar as possible to the version running in the SAAS Environment. Before running these steps, it is assumed you have a local development version of OpenHands running. #### Redis @@ -201,8 +201,8 @@ And then invoking `printenv`. NOTE: _DO NOT DO THIS WITH PROD!!!_ (Hopefully by "DEBUG": "1", "FILE_STORE": "local", "REDIS_HOST": "localhost:6379", - "OPENHANDS": "", - "FRONTEND_DIRECTORY": "/frontend/build", + "OPENHANDS": "", + "FRONTEND_DIRECTORY": "/frontend/build", "SANDBOX_RUNTIME_CONTAINER_IMAGE": "ghcr.io/openhands/runtime:main-nikolaik", "FILE_STORE_PATH": ">/.openhands-state", "OPENHANDS_CONFIG_CLS": "server.config.SaaSServerConfig", @@ -235,8 +235,8 @@ And then invoking `printenv`. NOTE: _DO NOT DO THIS WITH PROD!!!_ (Hopefully by "DEBUG": "1", "FILE_STORE": "local", "REDIS_HOST": "localhost:6379", - "OPENHANDS": "", - "FRONTEND_DIRECTORY": "/frontend/build", + "OPENHANDS": "", + "FRONTEND_DIRECTORY": "/frontend/build", "SANDBOX_RUNTIME_CONTAINER_IMAGE": "ghcr.io/openhands/runtime:main-nikolaik", "FILE_STORE_PATH": ">/.openhands-state", "OPENHANDS_CONFIG_CLS": "server.config.SaaSServerConfig", diff --git a/frontend/__tests__/MSW.md b/frontend/__tests__/MSW.md index f240c5a8df..828c2c6902 100644 --- a/frontend/__tests__/MSW.md +++ b/frontend/__tests__/MSW.md @@ -33,10 +33,10 @@ npm run dev:mock:saas These commands set `VITE_MOCK_API=true` which activates the MSW Service Worker to intercept requests. > [!NOTE] -> **OSS vs SaaS Mode** +> **OpenHands vs SaaS Mode** > > OpenHands runs in two modes: -> - **OSS mode**: For local/self-hosted deployments where users provide their own LLM API keys and configure git providers manually +> - **OpenHands mode**: For local/self-hosted deployments where users provide their own LLM API keys and configure git providers manually > - **SaaS mode**: For the cloud offering with billing, managed API keys, and OAuth-based GitHub integration > > Use `dev:mock:saas` when working on SaaS-specific features like billing, API key management, or subscription flows. diff --git a/openhands/app_server/app_conversation/app_conversation_router.py b/openhands/app_server/app_conversation/app_conversation_router.py index 29ae3f69d7..89f9d2699c 100644 --- a/openhands/app_server/app_conversation/app_conversation_router.py +++ b/openhands/app_server/app_conversation/app_conversation_router.py @@ -1,4 +1,4 @@ -"""Sandboxed Conversation router for OpenHands Server.""" +"""Sandboxed Conversation router for OpenHands App Server.""" import asyncio import logging diff --git a/openhands/app_server/config.py b/openhands/app_server/config.py index 291a7bb388..26f6648dfb 100644 --- a/openhands/app_server/config.py +++ b/openhands/app_server/config.py @@ -82,7 +82,7 @@ def get_openhands_provider_base_url() -> str | None: def _get_default_lifespan(): # Check legacy parameters for saas mode. If we are in SAAS mode do not apply - # OSS alembic migrations + # OpenHands alembic migrations if 'saas' in (os.getenv('OPENHANDS_CONFIG_CLS') or '').lower(): return None return OssAppLifespanService() diff --git a/openhands/app_server/event/event_router.py b/openhands/app_server/event/event_router.py index 40a0321f15..980b3ab47a 100644 --- a/openhands/app_server/event/event_router.py +++ b/openhands/app_server/event/event_router.py @@ -1,4 +1,4 @@ -"""Event router for OpenHands Server.""" +"""Event router for OpenHands App Server.""" from datetime import datetime from typing import Annotated diff --git a/openhands/app_server/event_callback/webhook_router.py b/openhands/app_server/event_callback/webhook_router.py index 62dd7bec16..eee1e66237 100644 --- a/openhands/app_server/event_callback/webhook_router.py +++ b/openhands/app_server/event_callback/webhook_router.py @@ -1,4 +1,4 @@ -"""Event Callback router for OpenHands Server.""" +"""Event Callback router for OpenHands App Server.""" import asyncio import importlib @@ -188,7 +188,7 @@ async def get_secret( if user_id: user_auth = await get_user_auth_for_user(user_id) else: - # OSS mode - use default user auth + # OpenHands (OSS mode) - use default user auth user_auth = DefaultUserAuth() # Create UserContext directly diff --git a/openhands/app_server/sandbox/sandbox_router.py b/openhands/app_server/sandbox/sandbox_router.py index d43b285074..4acb2b943c 100644 --- a/openhands/app_server/sandbox/sandbox_router.py +++ b/openhands/app_server/sandbox/sandbox_router.py @@ -1,4 +1,4 @@ -"""Runtime Containers router for OpenHands Server.""" +"""Runtime Containers router for OpenHands App Server.""" from typing import Annotated diff --git a/openhands/app_server/sandbox/sandbox_spec_router.py b/openhands/app_server/sandbox/sandbox_spec_router.py index 1708b82a90..f7f15e9dc7 100644 --- a/openhands/app_server/sandbox/sandbox_spec_router.py +++ b/openhands/app_server/sandbox/sandbox_spec_router.py @@ -1,4 +1,4 @@ -"""Runtime Images router for OpenHands Server.""" +"""Runtime Images router for OpenHands App Server.""" from typing import Annotated diff --git a/openhands/app_server/services/db_session_injector.py b/openhands/app_server/services/db_session_injector.py index 737e1ff879..fdc29d0e35 100644 --- a/openhands/app_server/services/db_session_injector.py +++ b/openhands/app_server/services/db_session_injector.py @@ -1,4 +1,4 @@ -"""Database configuration and session management for OpenHands Server.""" +"""Database configuration and session management for OpenHands App Server.""" import asyncio import logging diff --git a/openhands/app_server/user/auth_user_context.py b/openhands/app_server/user/auth_user_context.py index 7adf7f902a..e896fb0356 100644 --- a/openhands/app_server/user/auth_user_context.py +++ b/openhands/app_server/user/auth_user_context.py @@ -31,7 +31,7 @@ class AuthUserContext(UserContext): async def get_user_id(self) -> str | None: # If you have an auth object here you are logged in. If user_id is None - # it means we are in OSS mode. + # it means we are in OpenHands (OSS mode). user_id = await self.user_auth.get_user_id() return user_id diff --git a/openhands/app_server/user/user_router.py b/openhands/app_server/user/user_router.py index 8f2005c988..0d2ff1ab97 100644 --- a/openhands/app_server/user/user_router.py +++ b/openhands/app_server/user/user_router.py @@ -1,4 +1,4 @@ -"""User router for OpenHands Server. For the moment, this simply implements the /me endpoint.""" +"""User router for OpenHands App Server. For the moment, this simply implements the /me endpoint.""" from fastapi import APIRouter, HTTPException, status diff --git a/openhands/integrations/azure_devops/service/repos.py b/openhands/integrations/azure_devops/service/repos.py index ac7930acda..53efc324d1 100644 --- a/openhands/integrations/azure_devops/service/repos.py +++ b/openhands/integrations/azure_devops/service/repos.py @@ -15,7 +15,7 @@ class AzureDevOpsReposMixin(AzureDevOpsMixinBase): sort: str = 'updated', order: str = 'desc', public: bool = False, - app_mode: AppMode = AppMode.OSS, + app_mode: AppMode = AppMode.OPENHANDS, ) -> list[Repository]: """Search for repositories in Azure DevOps.""" # Get all repositories across all projects in the organization diff --git a/openhands/integrations/gitlab/service/repos.py b/openhands/integrations/gitlab/service/repos.py index 78018c3d9d..fdfbe1d8df 100644 --- a/openhands/integrations/gitlab/service/repos.py +++ b/openhands/integrations/gitlab/service/repos.py @@ -75,7 +75,7 @@ class GitLabReposMixin(GitLabMixinBase): sort: str = 'updated', order: str = 'desc', public: bool = False, - app_mode: AppMode = AppMode.OSS, + app_mode: AppMode = AppMode.OPENHANDS, ) -> list[Repository]: if public: # When public=True, query is a GitLab URL that we need to parse diff --git a/openhands/server/app.py b/openhands/server/app.py index 910d6961c7..d0dfa21f56 100644 --- a/openhands/server/app.py +++ b/openhands/server/app.py @@ -96,7 +96,7 @@ app.include_router(conversation_api_router) app.include_router(manage_conversation_api_router) app.include_router(settings_router) app.include_router(secrets_router) -if server_config.app_mode == AppMode.OSS: +if server_config.app_mode == AppMode.OPENHANDS: app.include_router(git_api_router) if server_config.enable_v1: app.include_router(v1_router.router) diff --git a/openhands/server/config/server_config.py b/openhands/server/config/server_config.py index abcbfce91e..bb6134e6aa 100644 --- a/openhands/server/config/server_config.py +++ b/openhands/server/config/server_config.py @@ -15,7 +15,7 @@ from openhands.utils.import_utils import get_impl class ServerConfig(ServerConfigInterface): config_cls = os.environ.get('OPENHANDS_CONFIG_CLS', None) - app_mode = AppMode.OSS + app_mode = AppMode.OPENHANDS posthog_client_key = 'phc_3ESMmY9SgqEAGBB6sMGK5ayYHkeUuknH2vP6FmWH9RA' github_client_id = os.environ.get('GITHUB_APP_CLIENT_ID', '') enable_billing = os.environ.get('ENABLE_BILLING', 'false') == 'true' diff --git a/openhands/server/types.py b/openhands/server/types.py index 5e6d2a9368..345f92acbf 100644 --- a/openhands/server/types.py +++ b/openhands/server/types.py @@ -12,9 +12,12 @@ from typing import Any, ClassVar, Protocol class AppMode(Enum): - OSS = 'oss' + OPENHANDS = 'oss' SAAS = 'saas' + # Backwards-compatible alias (deprecated): prefer AppMode.OPENHANDS + OSS = 'oss' + class SessionMiddlewareInterface(Protocol): """Protocol for session middleware classes.""" diff --git a/tests/unit/integrations/bitbucket/test_bitbucket_repos.py b/tests/unit/integrations/bitbucket/test_bitbucket_repos.py index dc7d01c6ac..46cbfdd1b6 100644 --- a/tests/unit/integrations/bitbucket/test_bitbucket_repos.py +++ b/tests/unit/integrations/bitbucket/test_bitbucket_repos.py @@ -43,7 +43,7 @@ async def test_search_repositories_url_parsing_standard_url(bitbucket_service): sort='updated', order='desc', public=True, - app_mode=AppMode.OSS, + app_mode=AppMode.OPENHANDS, ) # Verify the correct workspace/repo combination was extracted and passed @@ -81,7 +81,7 @@ async def test_search_repositories_url_parsing_with_extra_path_segments( sort='updated', order='desc', public=True, - app_mode=AppMode.OSS, + app_mode=AppMode.OPENHANDS, ) # Verify the correct workspace/repo combination was extracted from complex URL @@ -103,7 +103,7 @@ async def test_search_repositories_url_parsing_invalid_url(bitbucket_service): sort='updated', order='desc', public=True, - app_mode=AppMode.OSS, + app_mode=AppMode.OPENHANDS, ) # Should return empty list for invalid URL and not call API @@ -126,7 +126,7 @@ async def test_search_repositories_url_parsing_insufficient_path_segments( sort='updated', order='desc', public=True, - app_mode=AppMode.OSS, + app_mode=AppMode.OPENHANDS, ) # Should return empty list for insufficient path segments and not call API diff --git a/tests/unit/server/routes/test_mcp_routes.py b/tests/unit/server/routes/test_mcp_routes.py index 8677b8c85c..3f5fb68e80 100644 --- a/tests/unit/server/routes/test_mcp_routes.py +++ b/tests/unit/server/routes/test_mcp_routes.py @@ -48,7 +48,7 @@ async def test_get_conversation_link_non_saas_mode(): # Test with non-SAAS mode with patch('openhands.server.routes.mcp.server_config') as mock_config: - mock_config.app_mode = AppMode.OSS + mock_config.app_mode = AppMode.OPENHANDS # Call the function result = await get_conversation_link( diff --git a/tests/unit/server/services/test_conversation_service.py b/tests/unit/server/services/test_conversation_service.py index be247390ac..00b767319a 100644 --- a/tests/unit/server/services/test_conversation_service.py +++ b/tests/unit/server/services/test_conversation_service.py @@ -102,9 +102,9 @@ async def test_setup_with_provided_tokens_uses_real_tokens( @pytest.mark.asyncio async def test_setup_without_tokens_non_saas_uses_user_secrets(mock_settings): - """Test that OSS mode uses user_secrets.provider_tokens when no tokens provided. + """Test that OpenHands (OSS mode) uses user_secrets.provider_tokens when no tokens provided. - This test verifies OSS mode backward compatibility - tokens come from local config, not endpoint. + This test verifies OpenHands (OSS mode) backward compatibility - tokens come from local config, not endpoint. """ user_id = 'test_user_456' conversation_id = 'test_conv_123' @@ -140,7 +140,7 @@ async def test_setup_without_tokens_non_saas_uses_user_secrets(mock_settings): mock_secrets_store.load = AsyncMock(return_value=mock_user_secrets) mock_secrets_store_cls.return_value = mock_secrets_store - mock_server_config.app_mode = AppMode.OSS + mock_server_config.app_mode = AppMode.OPENHANDS # Call without endpoint tokens result = await setup_init_conversation_settings( diff --git a/tests/unit/test_forgejo_service.py b/tests/unit/test_forgejo_service.py index dee8d9bc27..d14e9fd755 100644 --- a/tests/unit/test_forgejo_service.py +++ b/tests/unit/test_forgejo_service.py @@ -75,7 +75,7 @@ async def test_search_repositories(forgejo_service): # Call the method repos = await forgejo_service.search_repositories( - 'test', 10, 'updated', 'desc', public=False, app_mode=AppMode.OSS + 'test', 10, 'updated', 'desc', public=False, app_mode=AppMode.OPENHANDS ) # Verify the result @@ -139,7 +139,7 @@ async def test_get_all_repositories(forgejo_service): ] # Call the method - repos = await forgejo_service.get_all_repositories('updated', AppMode.OSS) + repos = await forgejo_service.get_all_repositories('updated', AppMode.OPENHANDS) # Verify the result assert len(repos) == 3