refactor: move settings and secrets stores to app_server (#14165)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Tim O'Farrell
2026-04-27 13:54:33 -06:00
committed by GitHub
parent 3a40ecb931
commit 9e3aed7f53
21 changed files with 36 additions and 36 deletions

View File

@@ -1,49 +0,0 @@
from __future__ import annotations
import json
from dataclasses import dataclass
from openhands.core.config.openhands_config import OpenHandsConfig
from openhands.storage import get_file_store
from openhands.storage.data_models.secrets import Secrets
from openhands.storage.files import FileStore
from openhands.storage.secrets.secrets_store import SecretsStore
from openhands.utils.async_utils import call_sync_from_async
@dataclass
class FileSecretsStore(SecretsStore):
file_store: FileStore
path: str = 'secrets.json'
async def load(self) -> Secrets | None:
try:
json_str = await call_sync_from_async(self.file_store.read, self.path)
kwargs = json.loads(json_str)
provider_tokens = {
k: v
for k, v in (kwargs.get('provider_tokens') or {}).items()
if v.get('token')
}
kwargs['provider_tokens'] = provider_tokens
secrets = Secrets(**kwargs)
return secrets
except FileNotFoundError:
return None
async def store(self, secrets: Secrets) -> None:
json_str = secrets.model_dump_json(context={'expose_secrets': True})
await call_sync_from_async(self.file_store.write, self.path, json_str)
@classmethod
async def get_instance(
cls, config: OpenHandsConfig, user_id: str | None
) -> FileSecretsStore:
file_store = get_file_store(
file_store_type=config.file_store,
file_store_path=config.file_store_path,
file_store_web_hook_url=config.file_store_web_hook_url,
file_store_web_hook_headers=config.file_store_web_hook_headers,
file_store_web_hook_batch=config.file_store_web_hook_batch,
)
return FileSecretsStore(file_store)

View File

@@ -1,36 +0,0 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from openhands.core.config.openhands_config import OpenHandsConfig
from openhands.storage.data_models.secrets import Secrets
class SecretsStore(ABC):
"""Abstract base class for storing user secrets.
This is an extension point in OpenHands that allows applications to customize how
user secrets are stored. Applications can substitute their own implementation by:
1. Creating a class that inherits from SecretsStore
2. Implementing all required methods
3. Setting server_config.secret_store_class to the fully qualified name of the class
The class is instantiated via get_impl() in openhands.server.shared.py.
The implementation may or may not support multiple users depending on the environment.
"""
@abstractmethod
async def load(self) -> Secrets | None:
"""Load secrets."""
@abstractmethod
async def store(self, secrets: Secrets) -> None:
"""Store secrets."""
@classmethod
@abstractmethod
async def get_instance(
cls, config: OpenHandsConfig, user_id: str | None
) -> SecretsStore:
"""Get a store for the user represented by the token given."""

View File

@@ -1,50 +0,0 @@
from __future__ import annotations
import json
from dataclasses import dataclass
from openhands.core.config.openhands_config import OpenHandsConfig
from openhands.storage import get_file_store
from openhands.storage.data_models.settings import Settings
from openhands.storage.files import FileStore
from openhands.storage.settings.settings_store import SettingsStore
from openhands.utils.async_utils import call_sync_from_async
@dataclass
class FileSettingsStore(SettingsStore):
file_store: FileStore
path: str = 'settings.json'
async def load(self) -> Settings | None:
try:
json_str = await call_sync_from_async(self.file_store.read, self.path)
kwargs = json.loads(json_str)
settings = Settings(**kwargs)
# Turn on V1 in OpenHands
# We can simplify / remove this as part of V0 removal
settings.v1_enabled = True
return settings
except FileNotFoundError:
return None
async def store(self, settings: Settings) -> None:
json_str = settings.model_dump_json(
context={'expose_secrets': True, 'persist_settings': True}
)
await call_sync_from_async(self.file_store.write, self.path, json_str)
@classmethod
async def get_instance(
cls, config: OpenHandsConfig, user_id: str | None
) -> FileSettingsStore:
file_store = get_file_store(
file_store_type=config.file_store,
file_store_path=config.file_store_path,
file_store_web_hook_url=config.file_store_web_hook_url,
file_store_web_hook_headers=config.file_store_web_hook_headers,
file_store_web_hook_batch=config.file_store_web_hook_batch,
)
return FileSettingsStore(file_store)

View File

@@ -1,36 +0,0 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from openhands.core.config.openhands_config import OpenHandsConfig
from openhands.storage.data_models.settings import Settings
class SettingsStore(ABC):
"""Abstract base class for storing user settings.
This is an extension point in OpenHands that allows applications to customize how
user settings are stored. Applications can substitute their own implementation by:
1. Creating a class that inherits from SettingsStore
2. Implementing all required methods
3. Setting server_config.settings_store_class to the fully qualified name of the class
The class is instantiated via get_impl() in openhands.server.shared.py.
The implementation may or may not support multiple users depending on the environment.
"""
@abstractmethod
async def load(self) -> Settings | None:
"""Load session init data."""
@abstractmethod
async def store(self, settings: Settings) -> None:
"""Store session init data."""
@classmethod
@abstractmethod
async def get_instance(
cls, config: OpenHandsConfig, user_id: str | None
) -> SettingsStore:
"""Get a store for the user represented by the token given."""