Files
free-claude-code/providers/open_router/client.py
T
2026-04-30 00:33:45 -07:00

105 lines
3.6 KiB
Python

"""OpenRouter provider implementation."""
from __future__ import annotations
from collections.abc import Iterator
from typing import Any
from core.anthropic import append_request_id, iter_provider_stream_error_sse_events
from core.anthropic.native_sse_block_policy import (
NativeSseBlockPolicyState,
is_terminal_openrouter_done_event,
parse_native_sse_event,
transform_native_sse_block_event,
)
from providers.anthropic_messages import AnthropicMessagesTransport, StreamChunkMode
from providers.base import ProviderConfig
from providers.defaults import OPENROUTER_DEFAULT_BASE
from .request import build_request_body
_ANTHROPIC_VERSION = "2023-06-01"
class OpenRouterProvider(AnthropicMessagesTransport):
"""OpenRouter provider using the native Anthropic-compatible messages API."""
stream_chunk_mode: StreamChunkMode = "event"
def __init__(self, config: ProviderConfig):
super().__init__(
config,
provider_name="OPENROUTER",
default_base_url=OPENROUTER_DEFAULT_BASE,
)
def _build_request_body(
self, request: Any, thinking_enabled: bool | None = None
) -> dict:
"""Internal helper for tests and direct request dispatch."""
return build_request_body(
request,
thinking_enabled=self._is_thinking_enabled(request, thinking_enabled),
)
def _request_headers(self) -> dict[str, str]:
"""Return OpenRouter's Anthropic-compatible messages headers."""
return {
"Accept": "text/event-stream",
"Authorization": f"Bearer {self._api_key}",
"Content-Type": "application/json",
"anthropic-version": _ANTHROPIC_VERSION,
}
def _model_list_headers(self) -> dict[str, str]:
"""Return OpenRouter's OpenAI-compatible model-list headers."""
return {"Authorization": f"Bearer {self._api_key}"}
def _new_stream_state(self, request: Any, *, thinking_enabled: bool) -> Any:
"""Create per-stream state for thinking block filtering."""
return NativeSseBlockPolicyState()
def _transform_stream_event(
self,
event: str,
state: Any,
*,
thinking_enabled: bool,
) -> str | None:
"""Drop provider-specific terminal noise and hidden thinking events."""
if isinstance(state, NativeSseBlockPolicyState):
event_name, data_text = parse_native_sse_event(event)
if state.message_stopped or is_terminal_openrouter_done_event(
event_name, data_text
):
return None
if event_name == "message_stop":
state.message_stopped = True
if isinstance(state, NativeSseBlockPolicyState):
return transform_native_sse_block_event(
event, state, thinking_enabled=thinking_enabled
)
return event
def _format_error_message(self, base_message: str, request_id: str | None) -> str:
"""Keep OpenRouter's existing request-id suffix format."""
return append_request_id(base_message, request_id)
def _emit_error_events(
self,
*,
request: Any,
input_tokens: int,
error_message: str,
sent_any_event: bool,
) -> Iterator[str]:
"""Emit the Anthropic SSE error shape expected by Claude clients."""
yield from iter_provider_stream_error_sse_events(
request=request,
input_tokens=input_tokens,
error_message=error_message,
sent_any_event=sent_any_event,
log_raw_sse_events=self._config.log_raw_sse_events,
)