Protocol-Based Typed Service Access
===================================

MindRoot provides a Protocol-based system for typed service access, enabling IDE autocomplete
and type checking when calling services.

Overview
--------

MindRoot services are dynamically registered and called via ``service_manager``. While flexible,
this loses type information. The Protocols system restores type safety:

.. code-block:: python

    from mindroot.protocols import llm, image, tts

    # Full IDE autocomplete!
    stream = await llm.stream_chat('gpt-4', messages=[...], context=ctx)
    result = await image.generate('a red dragon', width=1024, context=ctx)
    audio = await tts.synthesize('Hello world', context=ctx)

Quick Start
-----------

Using Pre-instantiated Proxies (Recommended)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The simplest way to use typed services:

.. code-block:: python

    from mindroot.protocols import llm, image, tts, stt, web_search

    # These are lazy proxies - they initialize on first use
    stream = await llm.stream_chat('gpt-4', messages=[...], context=ctx)
    result = await image.generate('a red dragon', context=ctx)

Creating Your Own Typed Proxy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Alternatively, create a typed proxy explicitly:

.. code-block:: python

    from mindroot.protocols import LLM
    from mindroot.services import service_manager

    llm: LLM = service_manager.typed(LLM)
    stream = await llm.stream_chat('gpt-4', messages=[...], context=ctx)

Implementing Services with Protocols
------------------------------------

Class-Based Implementation (Recommended)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use ``@service_class`` to implement a Protocol with full IDE autocomplete:

.. code-block:: python

    from mindroot.services import service_class
    from mindroot.protocols import LLM
    from typing import AsyncIterator, Any

    @service_class(LLM)
    class MyLLM(LLM):
        """My LLM implementation."""
        
        async def stream_chat(
            self,
            model: str,
            messages: list = None,
            context: Any = None,
            num_ctx: int = 200000,
            temperature: float = 0.0,
            max_tokens: int = 5000,
            num_gpu_layers: int = 0
        ) -> AsyncIterator[str]:
            # IDE autocomplete works here!
            ...
        
        async def chat(self, model: str, messages: list,
                       context: Any = None) -> str:
            ...
        
        async def format_image_message(self, pil_image: Any,
                                       context: Any = None) -> dict:
            ...
        
        async def get_service_models(self, context: Any = None) -> dict:
            return {
                "stream_chat": ["my-model-1", "my-model-2"],
                "chat": ["my-model-1", "my-model-2"]
            }

The ``@service_class`` decorator will:

1. Instantiate the class (must have no required ``__init__`` args)
2. Find all methods defined in the Protocol
3. Register each matching method as a service

Function-Based Implementation (Legacy)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The traditional ``@service()`` decorator still works for standalone functions:

.. code-block:: python

    from mindroot.services import service

    @service()
    async def stream_chat(model, messages=[], context=None, ...):
        ...

This approach is fully backwards compatible but doesn't provide IDE autocomplete
for the function signature.

Available Protocols
-------------------

Core MindRoot provides these common Protocol definitions:

LLM (Language Model)
~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    class LLM(Protocol):
        async def stream_chat(self, model: str, messages: list = None,
                              context = None, temperature: float = 0.0,
                              max_tokens: int = 5000) -> AsyncIterator[str]: ...
        
        async def chat(self, model: str, messages: list,
                       context = None) -> str: ...
        
        async def format_image_message(self, pil_image, context = None) -> dict: ...
        
        async def get_service_models(self, context = None) -> dict: ...

Implemented by: ``ah_openai``, ``ah_anthropic``, ``ah_ollama``, ``mr_deepseek``, etc.

Image
~~~~~

.. code-block:: python

    class Image(Protocol):
        async def generate(self, prompt: str, width: int = 1024,
                          height: int = 1024, context = None) -> dict: ...

Implemented by: ``ah_flux``, ``ah_sd``, ``mr_imagen``, etc.

TTS (Text-to-Speech)
~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    class TTS(Protocol):
        async def synthesize(self, text: str, voice: str = 'default',
                            context = None) -> bytes: ...
        
        async def list_voices(self, context = None) -> list: ...

Implemented by: ``ah_tts``, ``mr_eleven_stream``, ``mr_f5_tts``, etc.

STT (Speech-to-Text)
~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    class STT(Protocol):
        async def transcribe(self, audio: bytes, context = None) -> str: ...
        
        async def transcribe_stream(self, audio_stream: AsyncIterator[bytes],
                                   context = None) -> AsyncIterator[str]: ...

Implemented by: ``mr_deepgram``, whisper plugins, etc.

WebSearch
~~~~~~~~~

.. code-block:: python

    class WebSearch(Protocol):
        async def search(self, query: str, num_results: int = 10,
                        context = None) -> list: ...

Plugin-Defined Protocols
------------------------

Plugins can define their own Protocols without modifying core MindRoot:

Defining a Protocol
~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    # In mr_sip/protocols.py
    from typing import Protocol, runtime_checkable

    @runtime_checkable
    class SIP(Protocol):
        """SIP telephony service protocol."""
        
        async def dial_service(self, destination: str,
                              context = None) -> dict: ...
        
        async def end_call_service(self, context = None) -> dict: ...
        
        async def sip_audio_out_chunk(self, audio_chunk: bytes,
                                      context = None) -> None: ...

Implementing with service_class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    # In mr_sip/mod.py
    from mindroot.services import service_class
    from .protocols import SIP

    @service_class(SIP)
    class MySIPProvider(SIP):
        async def dial_service(self, destination: str, context = None) -> dict:
            # Implementation with IDE autocomplete
            ...

Creating a Pre-instantiated Proxy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    # In mr_sip/__init__.py or mr_sip/protocols.py
    from mindroot.lib.providers.protocols.registry import create_lazy_proxy
    from .protocols import SIP

    # Create a lazy proxy for convenient access
    sip: SIP = create_lazy_proxy(SIP)

Using Plugin Protocols
~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    # Users can then import and use:
    from mr_sip.protocols import sip

    result = await sip.dial_service('555-1234', context=ctx)
    await sip.end_call_service(context=ctx)

Protocol Registry
-----------------

Plugins can optionally register their Protocols for discovery:

Registering a Protocol
~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    from mindroot.protocols import register_protocol
    from .protocols import SIP

    register_protocol('sip', SIP)

Discovering Protocols
~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    from mindroot.protocols import get_protocol, list_protocols

    # List all registered protocols
    protocols = list_protocols()  # {'llm': LLM, 'image': Image, ...}

    # Get a specific protocol
    SIP = get_protocol('sip')
    if SIP:
        from mindroot.services import service_manager
        sip = service_manager.typed(SIP)

Method-to-Service Mapping
~~~~~~~~~~~~~~~~~~~~~~~~~

When a Protocol method name differs from the service name, use explicit mapping:

.. code-block:: python

    from mindroot.protocols import map_method_to_service, Image

    # Map Image.generate() to the 'image' service
    map_method_to_service(Image, 'generate', 'image')

Backwards Compatibility
-----------------------

The Protocol system is fully backwards compatible. All existing code continues to work:

.. code-block:: python

    # This still works exactly as before
    from mindroot.services import service_manager

    stream = await service_manager.stream_chat('gpt-4', messages=[...], context=ctx)
    result = await service_manager.image('a red dragon', context=ctx)

    # Legacy imports also work
    from lib.providers.services import service_manager
    from lib.providers.protocols import llm

Import Paths
------------

MindRoot supports both short and full import paths:

.. code-block:: python

    # Short paths (recommended)
    from mindroot.services import service, service_class, service_manager
    from mindroot.protocols import LLM, Image, TTS, llm, image, tts

    # Full paths (also work)
    from mindroot.lib.providers.services import service, service_class, service_manager
    from mindroot.lib.providers.protocols import LLM, Image, TTS, llm, image, tts

API Reference
-------------

@service_class(protocol)
~~~~~~~~~~~~~~~~~~~~~~~~

Class decorator that registers all Protocol methods as services.

:param protocol: The Protocol class being implemented
:param flags: Optional flags to pass to each service registration

.. code-block:: python

    from mindroot.services import service_class
    from mindroot.protocols import LLM

    @service_class(LLM)
    class MyLLM(LLM):
        async def stream_chat(self, model: str, ...) -> AsyncIterator[str]:
            ...

@service()
~~~~~~~~~~

Function decorator for registering individual service functions.

:param flags: Optional flags for the service

.. code-block:: python

    from mindroot.services import service

    @service()
    async def my_function(arg1, context=None):
        ...

service_manager.typed(protocol)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Get a typed proxy for a service protocol.

:param protocol: A Protocol class defining the service interface
:returns: A proxy object typed as the Protocol

.. code-block:: python

    from mindroot.protocols import LLM
    from mindroot.services import service_manager

    llm: LLM = service_manager.typed(LLM)

service_manager.get_protocol(name)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Get a registered Protocol by name.

:param name: The protocol name (e.g., 'llm', 'sip')
:returns: The Protocol class, or None if not found

.. code-block:: python

    SIP = service_manager.get_protocol('sip')

service_manager.list_protocols()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

List all registered Protocols.

:returns: Dict mapping protocol names to Protocol classes

.. code-block:: python

    protocols = service_manager.list_protocols()
    # {'llm': LLM, 'image': Image, 'tts': TTS, ...}

create_lazy_proxy(protocol)
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Create a lazy typed proxy for a Protocol. Useful for plugins.

:param protocol: The Protocol class
:returns: A LazyTypedProxy typed as the Protocol

.. code-block:: python

    from mindroot.lib.providers.protocols.registry import create_lazy_proxy
    
    sip: SIP = create_lazy_proxy(SIP)

register_protocol(name, protocol_class)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Register a Protocol class for discovery.

:param name: A unique name for the protocol
:param protocol_class: The Protocol class

.. code-block:: python

    from mindroot.protocols import register_protocol

    register_protocol('sip', SIP)

map_method_to_service(protocol, method_name, service_name)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Create an explicit mapping from a Protocol method to a service name.

:param protocol: The Protocol class
:param method_name: The method name in the Protocol
:param service_name: The actual service name to call

.. code-block:: python

    from mindroot.protocols import map_method_to_service, Image

    map_method_to_service(Image, 'generate', 'image')
