mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
359 lines
12 KiB
Python
359 lines
12 KiB
Python
"""Configuration for the OpenHands App Server."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import AsyncContextManager
|
|
|
|
import httpx
|
|
from fastapi import Depends, Request
|
|
from pydantic import Field
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from openhands.agent_server.env_parser import from_env
|
|
from openhands.app_server.app_conversation.app_conversation_info_service import (
|
|
AppConversationInfoService,
|
|
AppConversationInfoServiceInjector,
|
|
)
|
|
from openhands.app_server.app_conversation.app_conversation_service import (
|
|
AppConversationService,
|
|
AppConversationServiceInjector,
|
|
)
|
|
from openhands.app_server.app_conversation.app_conversation_start_task_service import (
|
|
AppConversationStartTaskService,
|
|
AppConversationStartTaskServiceInjector,
|
|
)
|
|
from openhands.app_server.app_lifespan.app_lifespan_service import AppLifespanService
|
|
from openhands.app_server.app_lifespan.oss_app_lifespan_service import (
|
|
OssAppLifespanService,
|
|
)
|
|
from openhands.app_server.event.event_service import EventService, EventServiceInjector
|
|
from openhands.app_server.event_callback.event_callback_service import (
|
|
EventCallbackService,
|
|
EventCallbackServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.sandbox_service import (
|
|
SandboxService,
|
|
SandboxServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.sandbox_spec_service import (
|
|
SandboxSpecService,
|
|
SandboxSpecServiceInjector,
|
|
)
|
|
from openhands.app_server.services.db_session_injector import (
|
|
DbSessionInjector,
|
|
)
|
|
from openhands.app_server.services.httpx_client_injector import HttpxClientInjector
|
|
from openhands.app_server.services.injector import InjectorState
|
|
from openhands.app_server.services.jwt_service import JwtService, JwtServiceInjector
|
|
from openhands.app_server.user.user_context import UserContext, UserContextInjector
|
|
from openhands.sdk.utils.models import OpenHandsModel
|
|
|
|
|
|
def get_default_persistence_dir() -> Path:
|
|
# Recheck env because this function is also used to generate other defaults
|
|
persistence_dir = os.getenv('OH_PERSISTENCE_DIR')
|
|
|
|
if persistence_dir:
|
|
result = Path(persistence_dir)
|
|
else:
|
|
result = Path.home() / '.openhands'
|
|
|
|
result.mkdir(parents=True, exist_ok=True)
|
|
return result
|
|
|
|
|
|
def get_default_web_url() -> str | None:
|
|
"""Get legacy web host parameter.
|
|
|
|
If present, we assume we are running under https."""
|
|
web_host = os.getenv('WEB_HOST')
|
|
if not web_host:
|
|
return None
|
|
return f'https://{web_host}'
|
|
|
|
|
|
def _get_default_lifespan():
|
|
# Check legacy parameters for saas mode. If we are in SAAS mode do not apply
|
|
# OSS alembic migrations
|
|
if 'saas' in (os.getenv('OPENHANDS_CONFIG_CLS') or '').lower():
|
|
return None
|
|
return OssAppLifespanService()
|
|
|
|
|
|
class AppServerConfig(OpenHandsModel):
|
|
persistence_dir: Path = Field(default_factory=get_default_persistence_dir)
|
|
web_url: str | None = Field(
|
|
default_factory=get_default_web_url,
|
|
description='The URL where OpenHands is running (e.g., http://localhost:3000)',
|
|
)
|
|
# Dependency Injection Injectors
|
|
event: EventServiceInjector | None = None
|
|
event_callback: EventCallbackServiceInjector | None = None
|
|
sandbox: SandboxServiceInjector | None = None
|
|
sandbox_spec: SandboxSpecServiceInjector | None = None
|
|
app_conversation_info: AppConversationInfoServiceInjector | None = None
|
|
app_conversation_start_task: AppConversationStartTaskServiceInjector | None = None
|
|
app_conversation: AppConversationServiceInjector | None = None
|
|
user: UserContextInjector | None = None
|
|
jwt: JwtServiceInjector | None = None
|
|
httpx: HttpxClientInjector = Field(default_factory=HttpxClientInjector)
|
|
db_session: DbSessionInjector = Field(
|
|
default_factory=lambda: DbSessionInjector(
|
|
persistence_dir=get_default_persistence_dir()
|
|
)
|
|
)
|
|
|
|
# Services
|
|
lifespan: AppLifespanService | None = Field(default_factory=_get_default_lifespan)
|
|
|
|
|
|
def config_from_env() -> AppServerConfig:
|
|
# Import defaults...
|
|
from openhands.app_server.app_conversation.live_status_app_conversation_service import ( # noqa: E501
|
|
LiveStatusAppConversationServiceInjector,
|
|
)
|
|
from openhands.app_server.app_conversation.sql_app_conversation_info_service import ( # noqa: E501
|
|
SQLAppConversationInfoServiceInjector,
|
|
)
|
|
from openhands.app_server.app_conversation.sql_app_conversation_start_task_service import ( # noqa: E501
|
|
SQLAppConversationStartTaskServiceInjector,
|
|
)
|
|
from openhands.app_server.event.filesystem_event_service import (
|
|
FilesystemEventServiceInjector,
|
|
)
|
|
from openhands.app_server.event_callback.sql_event_callback_service import (
|
|
SQLEventCallbackServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.docker_sandbox_service import (
|
|
DockerSandboxServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.docker_sandbox_spec_service import (
|
|
DockerSandboxSpecServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.process_sandbox_service import (
|
|
ProcessSandboxServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.process_sandbox_spec_service import (
|
|
ProcessSandboxSpecServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.remote_sandbox_service import (
|
|
RemoteSandboxServiceInjector,
|
|
)
|
|
from openhands.app_server.sandbox.remote_sandbox_spec_service import (
|
|
RemoteSandboxSpecServiceInjector,
|
|
)
|
|
from openhands.app_server.user.auth_user_context import (
|
|
AuthUserContextInjector,
|
|
)
|
|
|
|
config: AppServerConfig = from_env(AppServerConfig, 'OH') # type: ignore
|
|
|
|
if config.event is None:
|
|
config.event = FilesystemEventServiceInjector()
|
|
|
|
if config.event_callback is None:
|
|
config.event_callback = SQLEventCallbackServiceInjector()
|
|
|
|
if config.sandbox is None:
|
|
# Legacy fallback
|
|
if os.getenv('RUNTIME') == 'remote':
|
|
config.sandbox = RemoteSandboxServiceInjector(
|
|
api_key=os.environ['SANDBOX_API_KEY'],
|
|
api_url=os.environ['SANDBOX_REMOTE_RUNTIME_API_URL'],
|
|
)
|
|
elif os.getenv('RUNTIME') in ('local', 'process'):
|
|
config.sandbox = ProcessSandboxServiceInjector()
|
|
else:
|
|
config.sandbox = DockerSandboxServiceInjector()
|
|
|
|
if config.sandbox_spec is None:
|
|
if os.getenv('RUNTIME') == 'remote':
|
|
config.sandbox_spec = RemoteSandboxSpecServiceInjector()
|
|
elif os.getenv('RUNTIME') in ('local', 'process'):
|
|
config.sandbox_spec = ProcessSandboxSpecServiceInjector()
|
|
else:
|
|
config.sandbox_spec = DockerSandboxSpecServiceInjector()
|
|
|
|
if config.app_conversation_info is None:
|
|
config.app_conversation_info = SQLAppConversationInfoServiceInjector()
|
|
|
|
if config.app_conversation_start_task is None:
|
|
config.app_conversation_start_task = (
|
|
SQLAppConversationStartTaskServiceInjector()
|
|
)
|
|
|
|
if config.app_conversation is None:
|
|
config.app_conversation = LiveStatusAppConversationServiceInjector()
|
|
|
|
if config.user is None:
|
|
config.user = AuthUserContextInjector()
|
|
|
|
if config.jwt is None:
|
|
config.jwt = JwtServiceInjector(persistence_dir=config.persistence_dir)
|
|
|
|
return config
|
|
|
|
|
|
_global_config: AppServerConfig | None = None
|
|
|
|
|
|
def get_global_config() -> AppServerConfig:
|
|
"""Get the default local server config shared across the server."""
|
|
global _global_config
|
|
if _global_config is None:
|
|
# Load configuration from environment...
|
|
_global_config = config_from_env()
|
|
|
|
return _global_config # type: ignore
|
|
|
|
|
|
def get_event_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[EventService]:
|
|
injector = get_global_config().event
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_event_callback_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[EventCallbackService]:
|
|
injector = get_global_config().event_callback
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_sandbox_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[SandboxService]:
|
|
injector = get_global_config().sandbox
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_sandbox_spec_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[SandboxSpecService]:
|
|
injector = get_global_config().sandbox_spec
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_app_conversation_info_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[AppConversationInfoService]:
|
|
injector = get_global_config().app_conversation_info
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_app_conversation_start_task_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[AppConversationStartTaskService]:
|
|
injector = get_global_config().app_conversation_start_task
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_app_conversation_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[AppConversationService]:
|
|
injector = get_global_config().app_conversation
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_user_context(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[UserContext]:
|
|
injector = get_global_config().user
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_httpx_client(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[httpx.AsyncClient]:
|
|
return get_global_config().httpx.context(state, request)
|
|
|
|
|
|
def get_jwt_service(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[JwtService]:
|
|
injector = get_global_config().jwt
|
|
assert injector is not None
|
|
return injector.context(state, request)
|
|
|
|
|
|
def get_db_session(
|
|
state: InjectorState, request: Request | None = None
|
|
) -> AsyncContextManager[AsyncSession]:
|
|
return get_global_config().db_session.context(state, request)
|
|
|
|
|
|
def get_app_lifespan_service() -> AppLifespanService | None:
|
|
config = get_global_config()
|
|
return config.lifespan
|
|
|
|
|
|
def depends_event_service():
|
|
injector = get_global_config().event
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_event_callback_service():
|
|
injector = get_global_config().event_callback
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_sandbox_service():
|
|
injector = get_global_config().sandbox
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_sandbox_spec_service():
|
|
injector = get_global_config().sandbox_spec
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_app_conversation_info_service():
|
|
injector = get_global_config().app_conversation_info
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_app_conversation_start_task_service():
|
|
injector = get_global_config().app_conversation_start_task
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_app_conversation_service():
|
|
injector = get_global_config().app_conversation
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_user_context():
|
|
injector = get_global_config().user
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_httpx_client():
|
|
return Depends(get_global_config().httpx.depends)
|
|
|
|
|
|
def depends_jwt_service():
|
|
injector = get_global_config().jwt
|
|
assert injector is not None
|
|
return Depends(injector.depends)
|
|
|
|
|
|
def depends_db_session():
|
|
return Depends(get_global_config().db_session.depends)
|