from __future__ import annotations from collections.abc import AsyncIterator from dataclasses import dataclass, field from typing import Any, Protocol, runtime_checkable from pydantic import BaseModel @dataclass(slots=True) class ToolCallResult: """Normalized tool call from any provider.""" id: str name: str input: dict[str, Any] thought_signature: str | None = None @dataclass(slots=True) class CompletionResult: """Normalized completion result returned by provider backends.""" content: Any = "" input_tokens: int = 0 output_tokens: int = 0 cache_creation_input_tokens: int = 0 cache_read_input_tokens: int = 0 finish_reason: str = "stop" tool_calls: list[ToolCallResult] = field(default_factory=list) thinking_content: str | None = None thinking_blocks: list[dict[str, Any]] = field(default_factory=list) reasoning_details: list[dict[str, Any]] = field(default_factory=list) raw_response: Any = None @dataclass(slots=True) class StreamChunk: """A single chunk in a streaming response.""" content: str = "" is_done: bool = False finish_reason: str | None = None output_tokens: int | None = None @runtime_checkable class ProviderBackend(Protocol): """Transport-agnostic interface for LLM providers. Credentials are baked into the underlying SDK client at backend construction time (see src/llm/registry.py), so these method signatures deliberately do not accept api_key / api_base. """ async def complete( self, *, model: str, messages: list[dict[str, Any]], max_tokens: int, temperature: float | None = None, stop: list[str] | None = None, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, response_format: type[BaseModel] | dict[str, Any] | None = None, thinking_budget_tokens: int | None = None, thinking_effort: str | None = None, max_output_tokens: int | None = None, extra_params: dict[str, Any] | None = None, ) -> CompletionResult: ... def stream( self, *, model: str, messages: list[dict[str, Any]], max_tokens: int, temperature: float | None = None, stop: list[str] | None = None, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, response_format: type[BaseModel] | dict[str, Any] | None = None, thinking_budget_tokens: int | None = None, thinking_effort: str | None = None, max_output_tokens: int | None = None, extra_params: dict[str, Any] | None = None, ) -> AsyncIterator[StreamChunk]: ...