mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-08 22:38:05 -05:00
Clarify OpenHands naming (replace “OSS” wording in docs and backend) (#12235)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Sandboxed Conversation router for OpenHands Server."""
|
||||
"""Sandboxed Conversation router for OpenHands App Server."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Event router for OpenHands Server."""
|
||||
"""Event router for OpenHands App Server."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Runtime Containers router for OpenHands Server."""
|
||||
"""Runtime Containers router for OpenHands App Server."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Runtime Images router for OpenHands Server."""
|
||||
"""Runtime Images router for OpenHands App Server."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user