from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass
class ToolCall:
    """Returned by complete() when the LLM requests a tool invocation instead of generating text."""
    name: str
    arguments: dict
    tool_call_id: str = ""

class ContextLengthExceededError(Exception):
    """Raised when a request exceeds the model's context window."""
    pass

class ProviderAuthError(Exception):
    """Raised when an API key is invalid or missing."""
    pass

class ProviderConnectionError(Exception):
    """Raised when the provider's API cannot be reached."""
    pass

class LLMProvider(ABC):
    """Abstract base class for LLM provider implementations."""

    @abstractmethod
    async def complete(self, model: str, messages: list[dict], tools: list[dict] | None = None) -> "str | ToolCall":
        """
        Send messages to the model and return the response text or a tool call request.

        Args:
            model: The model name.
            messages: Conversation history as role/content dicts.
            tools: Optional list of tool definitions in unified format (name, description,
                   parameters). Each provider converts this to its native format internally.

        Returns:
            The response text as a string, or a ToolCall if the model wants to invoke a tool.

        Raises:
            ContextLengthExceededError: if the request exceeds the model's context window.
            ProviderAuthError: if authentication fails.
            ProviderConnectionError: if the API cannot be reached.
        """
        pass

    @abstractmethod
    async def count_tokens(self, model: str, messages: list[dict]) -> int:
        """
        Count the number of tokens in the given messages for the specified model.

        Args:
            model: The model name (e.g., 'gpt-4o', 'claude-sonnet-4-6').
            messages: List of message dictionaries with 'role' and 'content' keys.

        Returns:
            The total token count for the messages.
        """
        pass
