import uuid from types import MappingProxyType from typing import Any from openhands.core.logger import openhands_logger as logger from openhands.events.action.message import MessageAction from openhands.experiments.experiment_manager import ExperimentManagerImpl from openhands.integrations.provider import ( CUSTOM_SECRETS_TYPE_WITH_JSON_SCHEMA, PROVIDER_TOKEN_TYPE, ProviderToken, ) from openhands.integrations.service_types import ProviderType from openhands.server.data_models.agent_loop_info import AgentLoopInfo from openhands.server.session.conversation_init_data import ConversationInitData from openhands.server.shared import ( ConversationStoreImpl, SecretsStoreImpl, SettingsStoreImpl, config, conversation_manager, server_config, ) from openhands.server.types import AppMode, LLMAuthenticationError, MissingSettingsError from openhands.storage.data_models.conversation_metadata import ( ConversationMetadata, ConversationTrigger, ) from openhands.storage.data_models.user_secrets import UserSecrets from openhands.utils.conversation_summary import get_default_conversation_title async def create_new_conversation( user_id: str | None, git_provider_tokens: PROVIDER_TOKEN_TYPE | None, custom_secrets: CUSTOM_SECRETS_TYPE_WITH_JSON_SCHEMA | None, selected_repository: str | None, selected_branch: str | None, initial_user_msg: str | None, image_urls: list[str] | None, replay_json: str | None, conversation_instructions: str | None = None, conversation_trigger: ConversationTrigger = ConversationTrigger.GUI, attach_convo_id: bool = False, git_provider: ProviderType | None = None, conversation_id: str | None = None, ) -> AgentLoopInfo: logger.info( 'Creating conversation', extra={ 'signal': 'create_conversation', 'user_id': user_id, 'trigger': conversation_trigger.value, }, ) logger.info('Loading settings') settings_store = await SettingsStoreImpl.get_instance(config, user_id) settings = await settings_store.load() logger.info('Settings loaded') session_init_args: dict[str, Any] = {} if settings: session_init_args = {**settings.__dict__, **session_init_args} # We could use litellm.check_valid_key for a more accurate check, # but that would run a tiny inference. if ( not settings.llm_api_key or settings.llm_api_key.get_secret_value().isspace() ): logger.warning(f'Missing api key for model {settings.llm_model}') raise LLMAuthenticationError( 'Error authenticating with the LLM provider. Please check your API key' ) else: logger.warning('Settings not present, not starting conversation') raise MissingSettingsError('Settings not found') session_init_args['git_provider_tokens'] = git_provider_tokens session_init_args['selected_repository'] = selected_repository session_init_args['custom_secrets'] = custom_secrets session_init_args['selected_branch'] = selected_branch session_init_args['git_provider'] = git_provider session_init_args['conversation_instructions'] = conversation_instructions conversation_init_data = ConversationInitData(**session_init_args) logger.info('Loading conversation store') conversation_store = await ConversationStoreImpl.get_instance(config, user_id) logger.info('ServerConversation store loaded') # For nested runtimes, we allow a single conversation id, passed in on container creation if conversation_id is None: conversation_id = uuid.uuid4().hex if not await conversation_store.exists(conversation_id): logger.info( f'New conversation ID: {conversation_id}', extra={'user_id': user_id, 'session_id': conversation_id}, ) conversation_init_data = ExperimentManagerImpl.run_conversation_variant_test( user_id, conversation_id, conversation_init_data ) conversation_title = get_default_conversation_title(conversation_id) logger.info(f'Saving metadata for conversation {conversation_id}') await conversation_store.save_metadata( ConversationMetadata( trigger=conversation_trigger, conversation_id=conversation_id, title=conversation_title, user_id=user_id, selected_repository=selected_repository, selected_branch=selected_branch, git_provider=git_provider, llm_model=conversation_init_data.llm_model, ) ) logger.info( f'Starting agent loop for conversation {conversation_id}', extra={'user_id': user_id, 'session_id': conversation_id}, ) initial_message_action = None if initial_user_msg or image_urls: initial_message_action = MessageAction( content=initial_user_msg or '', image_urls=image_urls or [], ) if attach_convo_id: logger.warning('Attaching convo ID is deprecated, skipping process') agent_loop_info = await conversation_manager.maybe_start_agent_loop( conversation_id, conversation_init_data, user_id, initial_user_msg=initial_message_action, replay_json=replay_json, ) logger.info(f'Finished initializing conversation {agent_loop_info.conversation_id}') return agent_loop_info def create_provider_tokens_object( providers_set: list[ProviderType], ) -> PROVIDER_TOKEN_TYPE: """Create provider tokens object for the given providers.""" provider_information: dict[ProviderType, ProviderToken] = {} for provider in providers_set: provider_information[provider] = ProviderToken(token=None, user_id=None) return MappingProxyType(provider_information) async def setup_init_convo_settings( user_id: str | None, conversation_id: str, providers_set: list[ProviderType] ) -> ConversationInitData: """Set up conversation initialization data with provider tokens. Args: user_id: The user ID conversation_id: The conversation ID providers_set: List of provider types to set up tokens for Returns: ConversationInitData with provider tokens configured """ settings_store = await SettingsStoreImpl.get_instance(config, user_id) settings = await settings_store.load() secrets_store = await SecretsStoreImpl.get_instance(config, user_id) user_secrets: UserSecrets | None = await secrets_store.load() if not settings: from socketio.exceptions import ConnectionRefusedError raise ConnectionRefusedError( 'Settings not found', {'msg_id': 'CONFIGURATION$SETTINGS_NOT_FOUND'} ) session_init_args: dict = {} session_init_args = {**settings.__dict__, **session_init_args} git_provider_tokens = create_provider_tokens_object(providers_set) logger.info(f'Git provider scaffold: {git_provider_tokens}') if server_config.app_mode != AppMode.SAAS and user_secrets: git_provider_tokens = user_secrets.provider_tokens session_init_args['git_provider_tokens'] = git_provider_tokens if user_secrets: session_init_args['custom_secrets'] = user_secrets.custom_secrets convo_init_data = ConversationInitData(**session_init_args) # We should recreate the same experiment conditions when restarting a conversation return ExperimentManagerImpl.run_conversation_variant_test( user_id, conversation_id, convo_init_data )