def session_start(self, session: str = "HOL", **kwargs) -> str:
        """
        start a new session

        >>> isabelle_client = IsabelleClient("localhost", 9998, "test")
        >>> print(isabelle_client.session_start(verbose=True))
        Traceback (most recent call last):
            ...
        ValueError: Unexpected response type: FAILED
        >>> isabelle_client = IsabelleClient("localhost", 9999, "test")
        >>> print(isabelle_client.session_start())
        test_session_id

        :param session: a name of a session to start
        :param kwargs: additional arguments
            (see Isabelle System manual for details)
        :returns: a ``session_id``
        """
        arguments = {"session": session}
        arguments.update(kwargs)
        response_list = async_run(
            self.execute_command(f"session_start {json.dumps(arguments)}"))
        if response_list[-1].response_type == "FINISHED":
            return json.loads(response_list[-1].response_body)["session_id"]
        raise ValueError(
            f"Unexpected response type: {response_list[-1].response_type}")
    def help(self) -> List[IsabelleResponse]:
        """
        asks a server to display the list of available commands

        >>> isabelle_client = IsabelleClient("localhost", 9999, "test")
        >>> test_response = isabelle_client.help()
        >>> print(test_response[-1].response_body)
        ["echo", "help"]

        :returns: Isabelle server response
        """
        response = async_run(self.execute_command("help", asynchronous=False))
        return response
    def session_stop(self, session_id: str) -> List[IsabelleResponse]:
        """
        stop session with given ID

        >>> isabelle_client = IsabelleClient("localhost", 9999, "test")
        >>> test_response = isabelle_client.session_stop("test")
        >>> print(test_response[-1].response_type)
        FINISHED

        :param session_id: a string ID of a session
        :returns: Isabelle server response
        """
        arguments = json.dumps({"session_id": session_id})
        response = async_run(self.execute_command(f"session_stop {arguments}"))
        return response
    def echo(self, message: Any) -> List[IsabelleResponse]:
        """
        asks a server to echo a message

        >>> isabelle_client = IsabelleClient("localhost", 9999, "test")
        >>> test_response = isabelle_client.echo("test_message")
        >>> print(test_response[-1].response_body)
        "test_message"

        :param message: any text
        :returns: Isabelle server response
        """
        response = async_run(
            self.execute_command(f"echo {json. dumps(message)}",
                                 asynchronous=False))
        return response
    def cancel(self, task: str) -> List[IsabelleResponse]:
        """
        asks a server to try to cancel a task with a given ID

        >>> isabelle_client = IsabelleClient("localhost", 9999, "test")
        >>> test_response = isabelle_client.cancel("test_task")
        >>> print(test_response[-1].response_body)
        <BLANKLINE>

        :param task: a task ID
        :returns: Isabelle server response
        """
        arguments = {"task": task}
        response = async_run(
            self.execute_command(f"cancel {json.dumps(arguments)}",
                                 asynchronous=False))
        return response
    def use_theories(
        self,
        theories: List[str],
        session_id: Optional[str] = None,
        master_dir: Optional[str] = None,
        **kwargs,
    ) -> List[IsabelleResponse]:
        """
        run the engine on theory files

        >>> isabelle_client = IsabelleClient("localhost", 9999, "test")
        >>> test_response = isabelle_client.use_theories(
        ...     ["test"], master_dir="test", watchdog_timeout=0
        ... )
        >>> print(test_response[-1].response_type)
        FINISHED

        :param theories: names of theory files (without extensions!)
        :param session_id: an ID of a session; if ``None``, a new session is
            created and then destroyed after trying to process theories
        :param master_dir: where to look for theory files; if ``None``, uses a
            temp folder of the session
        :param kwargs: additional arguments
            (see Isabelle System manual for details)
        :returns: Isabelle server response
        """
        new_session_id = (self.session_start()
                          if session_id is None else session_id)
        arguments: Dict[str, Union[List[str], int, str]] = {
            "session_id": new_session_id,
            "theories": theories,
        }
        arguments.update(kwargs)
        if master_dir is not None:
            arguments["master_dir"] = master_dir
        response = async_run(
            self.execute_command(f"use_theories {json.dumps(arguments)}"))
        if session_id is None:
            self.session_stop(new_session_id)
        return response
    def purge_theories(
        self,
        session_id: str,
        theories: List[str],
        master_dir: Optional[str] = None,
        purge_all: Optional[bool] = None,
    ) -> List[IsabelleResponse]:
        """
        asks a server to purge listed theories from it

        >>> isabelle_client = IsabelleClient("localhost", 9999, "test")
        >>> test_response = isabelle_client.purge_theories(
        ...     "test", [], "dir", True
        ... )
        >>> print(test_response[-1].response_body)
        {"purged": [], "retained": []}

        :param session_id: an ID of the session from which to purge theories
        :param theories: a list of theory names to purge from the server
        :param master_dir:  the master directory as in ``use_theories``
        :param purge_all: set to ``True`` attempts to purge all presently
            loaded theories
        :returns: Isabelle server response
        """
        arguments: Dict[str, Union[str, List[str], bool]] = {
            "session_id": session_id,
            "theories": theories,
        }
        if master_dir is not None:
            arguments["master_dir"] = master_dir
        if purge_all is not None:
            arguments["all"] = purge_all
        response = async_run(
            self.execute_command(f"purge_theories {json.dumps(arguments)}",
                                 asynchronous=False))
        return response