Repository: pydantic/pydantic-ai
File: pydantic_ai_slim/pydantic_ai/models/openrouter.py (Python)

Code before:


class _OpenRouterChatCompletionMessage(BaseModel):
    content: str | None = None
    """The contents of the message."""

    refusal: str | None = None
    """The refusal message generated by the model."""

    role: Literal['assistant']
    """The role of the author of this message."""

    annotations: list[chat_completion_message.Annotation] | None = None
    """
    Annotations for the message, when applicable, as when using the
    [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat).
    """

    audio: chat.ChatCompletionAudio | None = None
    """
    If the audio output modality is requested, this object contains data about the
    audio response from the model.
    [Learn more](https://platform.openai.com/docs/guides/audio).
    """

    function_call: chat_completion_message.FunctionCall | None = None
    """Deprecated and replaced by `tool_calls`.

    The name and arguments of a function that should be called, as generated by the
    model.
    """

    tool_calls: list[_OpenRouterChatCompletionMessageToolCallUnion] | None = None
    """The tool calls generated by the model, such as function calls."""


class _OpenRouterCompletionMessage(_OpenRouterChatCompletionMessage):
    """Wrapped chat completion message with OpenRouter specific attributes."""

    reasoning: str | None = None
    """The reasoning text associated with the message, if any."""

    reasoning_details: list[_OpenRouterReasoningDetail] | None = None
    """The reasoning details associated with the message, if any."""


class _OpenRouterChoice(chat_completion.Choice):
    """Wraps OpenAI chat completion choice with OpenRouter specific attributes."""

    native_finish_reason: str
    """The provided finish reason by the downstream provider from OpenRouter."""

Review comment (suggestion):
We shouldn't need to repeat all these fields. On the existing `_OpenRouterCompletionMessage`, I believe we can just add `tool_calls` with the new type, and add `# type: ignore[reportIncompatibleVariableOverride]` like we have on the overridden `finish_reason` field.

Code after:
    """Wraps OpenAI chat completion choice with OpenRouter specific attributes."""

    native_finish_reason: str | None
    """The provided finish reason by the downstream provider from OpenRouter."""

    finish_reason: Literal['stop', 'length', 'tool_calls', 'content_filter', 'error']  # type: ignore[reportIncompatibleVariableOverride]
    """OpenRouter specific finish reasons.

    Notably, removes 'function_call' and adds 'error'  finish reasons.
    """

    message: _OpenRouterCompletionMessage  # type: ignore[reportIncompatibleVariableOverride]
    """A wrapped chat completion message with OpenRouter specific attributes."""


@dataclass
class _OpenRouterCostDetails:
    """OpenRouter specific cost details."""

    upstream_inference_cost: int | None = None


class _OpenRouterPromptTokenDetails(completion_usage.PromptTokensDetails):
    """Wraps OpenAI completion token details with OpenRouter specific attributes."""

    video_tokens: int | None = None


class _OpenRouterCompletionTokenDetails(completion_usage.CompletionTokensDetails):
    """Wraps OpenAI completion token details with OpenRouter specific attributes."""

    image_tokens: int | None = None


class _OpenRouterUsage(completion_usage.CompletionUsage):
    """Wraps OpenAI completion usage with OpenRouter specific attributes."""

    cost: float | None = None

    cost_details: _OpenRouterCostDetails | None = None

    is_byok: bool | None = None

    prompt_tokens_details: _OpenRouterPromptTokenDetails | None = None  # type: ignore[reportIncompatibleVariableOverride]

    completion_tokens_details: _OpenRouterCompletionTokenDetails | None = None  # type: ignore[reportIncompatibleVariableOverride]


class _OpenRouterChatCompletion(chat.ChatCompletion):
    """Wraps OpenAI chat completion with OpenRouter specific attributes."""