Clarify OpenHands naming (replace “OSS” wording in docs and backend) (#12235)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Engel Nyst
2026-01-07 07:24:27 +01:00
committed by GitHub
parent b816d0448b
commit 08df955ba7
23 changed files with 60 additions and 48 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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.

View File

@@ -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": "<YOUR LOCAL OSS OPENHANDS DIR>",
"FRONTEND_DIRECTORY": "<YOUR LOCAL OSS OPENHANDS DIR>/frontend/build",
"OPENHANDS": "<YOUR LOCAL OPENHANDS DIR>",
"FRONTEND_DIRECTORY": "<YOUR LOCAL OPENHANDS DIR>/frontend/build",
"SANDBOX_RUNTIME_CONTAINER_IMAGE": "ghcr.io/openhands/runtime:main-nikolaik",
"FILE_STORE_PATH": "<YOUR HOME DIRECTORY>>/.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": "<YOUR LOCAL OSS OPENHANDS DIR>",
"FRONTEND_DIRECTORY": "<YOUR LOCAL OSS OPENHANDS DIR>/frontend/build",
"OPENHANDS": "<YOUR LOCAL OPENHANDS DIR>",
"FRONTEND_DIRECTORY": "<YOUR LOCAL OPENHANDS DIR>/frontend/build",
"SANDBOX_RUNTIME_CONTAINER_IMAGE": "ghcr.io/openhands/runtime:main-nikolaik",
"FILE_STORE_PATH": "<YOUR HOME DIRECTORY>>/.openhands-state",
"OPENHANDS_CONFIG_CLS": "server.config.SaaSServerConfig",

View File

@@ -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.

View File

@@ -1,4 +1,4 @@
"""Sandboxed Conversation router for OpenHands Server."""
"""Sandboxed Conversation router for OpenHands App Server."""
import asyncio
import logging

View File

@@ -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()

View File

@@ -1,4 +1,4 @@
"""Event router for OpenHands Server."""
"""Event router for OpenHands App Server."""
from datetime import datetime
from typing import Annotated

View File

@@ -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

View File

@@ -1,4 +1,4 @@
"""Runtime Containers router for OpenHands Server."""
"""Runtime Containers router for OpenHands App Server."""
from typing import Annotated

View File

@@ -1,4 +1,4 @@
"""Runtime Images router for OpenHands Server."""
"""Runtime Images router for OpenHands App Server."""
from typing import Annotated

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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'

View File

@@ -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."""

View File

@@ -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

View File

@@ -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(

View File

@@ -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(

View File

@@ -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