Spaces:
				
			
			
	
			
			
		Build error
		
	
	
	
			
			
	
	
	
	
		
		
		Build error
		
	| from fastapi import APIRouter, Depends, status | |
| from fastapi.responses import JSONResponse | |
| from openhands.core.logger import openhands_logger as logger | |
| from openhands.integrations.provider import ( | |
| PROVIDER_TOKEN_TYPE, | |
| ProviderType, | |
| ) | |
| from openhands.server.dependencies import get_dependencies | |
| from openhands.server.routes.secrets import invalidate_legacy_secrets_store | |
| from openhands.server.settings import ( | |
| GETSettingsModel, | |
| ) | |
| from openhands.server.shared import config | |
| from openhands.server.user_auth import ( | |
| get_provider_tokens, | |
| get_secrets_store, | |
| get_user_settings, | |
| get_user_settings_store, | |
| ) | |
| from openhands.storage.data_models.settings import Settings | |
| from openhands.storage.secrets.secrets_store import SecretsStore | |
| from openhands.storage.settings.settings_store import SettingsStore | |
| app = APIRouter(prefix='/api', dependencies=get_dependencies()) | |
| async def load_settings( | |
| provider_tokens: PROVIDER_TOKEN_TYPE | None = Depends(get_provider_tokens), | |
| settings_store: SettingsStore = Depends(get_user_settings_store), | |
| settings: Settings = Depends(get_user_settings), | |
| secrets_store: SecretsStore = Depends(get_secrets_store), | |
| ) -> GETSettingsModel | JSONResponse: | |
| try: | |
| if not settings: | |
| return JSONResponse( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| content={'error': 'Settings not found'}, | |
| ) | |
| # On initial load, user secrets may not be populated with values migrated from settings store | |
| user_secrets = await invalidate_legacy_secrets_store( | |
| settings, settings_store, secrets_store | |
| ) | |
| # If invalidation is successful, then the returned user secrets holds the most recent values | |
| git_providers = ( | |
| user_secrets.provider_tokens if user_secrets else provider_tokens | |
| ) | |
| provider_tokens_set: dict[ProviderType, str | None] = {} | |
| if git_providers: | |
| for provider_type, provider_token in git_providers.items(): | |
| if provider_token.token or provider_token.user_id: | |
| provider_tokens_set[provider_type] = provider_token.host | |
| settings_with_token_data = GETSettingsModel( | |
| **settings.model_dump(exclude='secrets_store'), | |
| llm_api_key_set=settings.llm_api_key is not None | |
| and bool(settings.llm_api_key), | |
| search_api_key_set=settings.search_api_key is not None | |
| and bool(settings.search_api_key), | |
| provider_tokens_set=provider_tokens_set, | |
| ) | |
| settings_with_token_data.llm_api_key = None | |
| settings_with_token_data.search_api_key = None | |
| return settings_with_token_data | |
| except Exception as e: | |
| logger.warning(f'Invalid token: {e}') | |
| # Get user_id from settings if available | |
| user_id = getattr(settings, 'user_id', 'unknown') if settings else 'unknown' | |
| logger.info( | |
| f'Returning 401 Unauthorized - Invalid token for user_id: {user_id}' | |
| ) | |
| return JSONResponse( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| content={'error': 'Invalid token'}, | |
| ) | |
| async def reset_settings() -> JSONResponse: | |
| """ | |
| Resets user settings. (Deprecated) | |
| """ | |
| logger.warning('Deprecated endpoint /api/reset-settings called by user') | |
| return JSONResponse( | |
| status_code=status.HTTP_410_GONE, | |
| content={'error': 'Reset settings functionality has been removed.'}, | |
| ) | |
| async def store_llm_settings( | |
| settings: Settings, settings_store: SettingsStore | |
| ) -> Settings: | |
| existing_settings = await settings_store.load() | |
| # Convert to Settings model and merge with existing settings | |
| if existing_settings: | |
| # Keep existing LLM settings if not provided | |
| if settings.llm_api_key is None: | |
| settings.llm_api_key = existing_settings.llm_api_key | |
| if settings.llm_model is None: | |
| settings.llm_model = existing_settings.llm_model | |
| if settings.llm_base_url is None: | |
| settings.llm_base_url = existing_settings.llm_base_url | |
| # Keep existing search API key if not provided | |
| if settings.search_api_key is None: | |
| settings.search_api_key = existing_settings.search_api_key | |
| return settings | |
| # NOTE: We use response_model=None for endpoints that return JSONResponse directly. | |
| # This is because FastAPI's response_model expects a Pydantic model, but we're returning | |
| # a response object directly. We document the possible responses using the 'responses' | |
| # parameter and maintain proper type annotations for mypy. | |
| async def store_settings( | |
| settings: Settings, | |
| settings_store: SettingsStore = Depends(get_user_settings_store), | |
| ) -> JSONResponse: | |
| # Check provider tokens are valid | |
| try: | |
| existing_settings = await settings_store.load() | |
| # Convert to Settings model and merge with existing settings | |
| if existing_settings: | |
| settings = await store_llm_settings(settings, settings_store) | |
| # Keep existing analytics consent if not provided | |
| if settings.user_consents_to_analytics is None: | |
| settings.user_consents_to_analytics = ( | |
| existing_settings.user_consents_to_analytics | |
| ) | |
| # Update sandbox config with new settings | |
| if settings.remote_runtime_resource_factor is not None: | |
| config.sandbox.remote_runtime_resource_factor = ( | |
| settings.remote_runtime_resource_factor | |
| ) | |
| settings = convert_to_settings(settings) | |
| await settings_store.store(settings) | |
| return JSONResponse( | |
| status_code=status.HTTP_200_OK, | |
| content={'message': 'Settings stored'}, | |
| ) | |
| except Exception as e: | |
| logger.warning(f'Something went wrong storing settings: {e}') | |
| return JSONResponse( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| content={'error': 'Something went wrong storing settings'}, | |
| ) | |
| def convert_to_settings(settings_with_token_data: Settings) -> Settings: | |
| settings_data = settings_with_token_data.model_dump() | |
| # Filter out additional fields from `SettingsWithTokenData` | |
| filtered_settings_data = { | |
| key: value | |
| for key, value in settings_data.items() | |
| if key in Settings.model_fields # Ensures only `Settings` fields are included | |
| } | |
| # Convert the API keys to `SecretStr` instances | |
| filtered_settings_data['llm_api_key'] = settings_with_token_data.llm_api_key | |
| filtered_settings_data['search_api_key'] = settings_with_token_data.search_api_key | |
| # Create a new Settings instance | |
| settings = Settings(**filtered_settings_data) | |
| return settings | |