Coverage for src / lexigram / contracts / ai / relay / types.py: 6%
62 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""Shared types for the relay protocol conversion engine.
3This module owns the stable enums, JSON aliases, usage accounting, loss
4records, conversion results, and payload unions that cross every relay
5wire format. Concrete wire DTOs live in ``relay.dto``; the canonical IR
6lives in ``relay.ir``; engine-side services live in the
7``lexigram-ai-relay`` extension package.
8"""
10from __future__ import annotations
12from dataclasses import dataclass, field
13from enum import StrEnum
14from typing import Any, Generic, TypeAlias, TypeVar
16from lexigram.contracts.ai.llm import TokenUsage
17from lexigram.contracts.ai.relay.dto import (
18 ClaudeRequest,
19 ClaudeResponse,
20 GeminiRequest,
21 GeminiResponse,
22 OpenAIChatRequest,
23 OpenAIChatResponse,
24 ResponsesRequest,
25 ResponsesResponse,
26)
28__all__ = [
29 "ConversionQuality",
30 "JsonObject",
31 "JsonValue",
32 "RelayConvertResult",
33 "RelayFormat",
34 "RelayLoss",
35 "RelayRequestPayload",
36 "RelayResponsePayload",
37 "RelayUsage",
38]
41class RelayFormat(StrEnum):
42 """The four text wire protocols supported by the relay engine.
44 Attributes:
45 OPENAI_CHAT: OpenAI Chat Completions (``/v1/chat/completions``).
46 OPENAI_RESPONSES: OpenAI Responses (``/v1/responses``).
47 CLAUDE: Anthropic Messages (``/v1/messages``).
48 GEMINI: Google Gemini ``generateContent``.
49 """
51 OPENAI_CHAT = "openai_chat"
52 OPENAI_RESPONSES = "openai_responses"
53 CLAUDE = "claude"
54 GEMINI = "gemini"
57class ConversionQuality(StrEnum):
58 """Semantic closeness between two wire protocols.
60 Attributes:
61 GOOD: Core structures are close; lossless in practice.
62 FAIR: Main capabilities convert, some features adapt or drop.
63 DISCOURAGED: Requires a multi-hop path; higher semantic-loss risk.
64 """
66 GOOD = "GOOD"
67 FAIR = "FAIR"
68 DISCOURAGED = "DISCOURAGED"
71JsonValue: TypeAlias = dict[str, Any] | list[Any] | str | int | float | bool | None
72"""A JSON-compatible value."""
74JsonObject: TypeAlias = dict[str, JsonValue]
75"""A JSON object (wire request/response fragment)."""
78@dataclass(frozen=True)
79class RelayUsage:
80 """Unified token usage, normalized across upstream response formats.
82 Attributes:
83 prompt_tokens: Input tokens (total, all subcategories).
84 completion_tokens: Output tokens (total, all subcategories).
85 cache_read_tokens: Cached input tokens (Claude ``cache_read``, OpenAI ``cached_tokens``).
86 cache_creation_tokens: Cache-creation input tokens (Claude only).
87 reasoning_tokens: Output tokens spent on reasoning (Claude/Gemini thinking).
88 audio_input_tokens: Audio input tokens (OpenAI audio models).
89 audio_output_tokens: Audio output tokens (OpenAI audio models).
90 image_tokens: Image input tokens (OpenAI image models).
91 input_tokens: Responses-style input count carried by the source
92 (Claude stamps prompt+cache; Gemini leaves it zero).
93 output_tokens: Responses-style output count carried by the source
94 (only the OpenAI response format stamps it).
95 total_tokens_override: Explicit total when the source reports one
96 that is not the sum of prompt and completion (Gemini counts
97 thinking tokens inside both).
98 """
100 prompt_tokens: int = 0
101 completion_tokens: int = 0
102 cache_read_tokens: int = 0
103 cache_creation_tokens: int = 0
104 reasoning_tokens: int = 0
105 audio_input_tokens: int = 0
106 audio_output_tokens: int = 0
107 image_tokens: int = 0
108 input_tokens: int = 0
109 output_tokens: int = 0
110 total_tokens_override: int | None = None
112 @property
113 def total_tokens(self) -> int:
114 """Total tokens consumed (prompt + completion or explicit override)."""
115 if self.total_tokens_override is not None:
116 return self.total_tokens_override
117 return self.prompt_tokens + self.completion_tokens
119 def to_token_usage(self) -> TokenUsage:
120 """Map to the shared ``TokenUsage`` without double counting.
122 Returns:
123 A ``TokenUsage`` with the derived total; detailed sub-category
124 counts remain available on this object.
125 """
126 return TokenUsage(
127 prompt_tokens=self.prompt_tokens,
128 completion_tokens=self.completion_tokens,
129 total_tokens=self.total_tokens,
130 )
133@dataclass(frozen=True)
134class RelayLoss:
135 """A semantic loss recorded during conversion.
137 Attributes:
138 field: Source wire field (or feature) that was dropped or adapted.
139 target: Target format the loss applies to.
140 reason: Machine-readable reason (e.g. ``json_mode_not_supported``).
141 severity: ``error``, ``warning``, or ``info``.
142 """
144 field: str
145 target: RelayFormat
146 reason: str
147 severity: str = "warning"
150T = TypeVar("T")
153@dataclass(frozen=True)
154class RelayConvertResult(Generic[T]):
155 """Outcome of a conversion with audit metadata.
157 Attributes:
158 value: The converted object.
159 source: Source wire format.
160 target: Target wire format.
161 converter_id: Converter identifier (``"<src>_to_<dst>"``).
162 quality: Semantic closeness of the conversion.
163 steps: Actual conversion path taken (source, canonical_ir, target,
164 plus named adaptations when present).
165 usage: Normalized usage when converting responses; ``None`` otherwise.
166 losses: Semantic losses recorded during conversion.
167 warnings: Human-readable compatibility notes.
168 """
170 value: T
171 source: RelayFormat
172 target: RelayFormat
173 converter_id: str
174 quality: ConversionQuality
175 steps: tuple[str, ...] = field(default_factory=tuple)
176 usage: RelayUsage | None = None
177 losses: tuple[RelayLoss, ...] = field(default_factory=tuple)
178 warnings: tuple[str, ...] = field(default_factory=tuple)
181RelayRequestPayload: TypeAlias = (
182 OpenAIChatRequest | ResponsesRequest | ClaudeRequest | GeminiRequest
183)
184"""Union of every request wire DTO the relay engine accepts."""
186RelayResponsePayload: TypeAlias = (
187 OpenAIChatResponse | ResponsesResponse | ClaudeResponse | GeminiResponse
188)
189"""Union of every non-stream response wire DTO the relay engine emits."""