Coverage for src / lexigram / contracts / ai / relay / protocols.py: 0%

36 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-15 18:57 +0800

1"""Relay conversion service protocols. 

2 

3Gateway and LLM packages consume these protocols from the container and 

4never import concrete relay implementation modules. The concrete engine 

5in ``lexigram-ai-relay`` implements them. 

6""" 

7 

8from __future__ import annotations 

9 

10from typing import Any, Protocol, TypeAlias, runtime_checkable 

11 

12from lexigram.contracts.ai.exceptions import RelayError 

13from lexigram.contracts.ai.relay.context import RelayConversionContext 

14from lexigram.contracts.ai.relay.types import ( 

15 ConversionQuality, 

16 RelayConvertResult, 

17 RelayFormat, 

18 RelayRequestPayload, 

19 RelayResponsePayload, 

20) 

21from lexigram.contracts.core.result import Result 

22 

23__all__ = [ 

24 "RelayConverterProtocol", 

25 "RelayMapperProtocol", 

26 "RelayRegistryProtocol", 

27 "RelayStreamOptions", 

28 "RelayStreamSessionProtocol", 

29] 

30 

31 

32RelayStreamOptions: TypeAlias = dict[str, Any] 

33"""Stream options (include_usage, per-target knobs) for a stream session.""" 

34 

35 

36@runtime_checkable 

37class RelayConverterProtocol(Protocol): 

38 """Explicit source/target protocol conversion and stream ownership. 

39 

40 The engine is synchronous, side-effect free, and performs no HTTP, 

41 channel selection, billing, or model selection. Callers supply the 

42 already-selected upstream model and any host callbacks via context. 

43 """ 

44 

45 def convert_request( 

46 self, 

47 payload: RelayRequestPayload, 

48 source: RelayFormat, 

49 target: RelayFormat, 

50 *, 

51 context: RelayConversionContext | None = None, 

52 registry: RelayRegistryProtocol | None = None, 

53 ) -> Result[RelayConvertResult[RelayRequestPayload], RelayError]: 

54 """Convert a request payload from *source* to *target*.""" 

55 ... 

56 

57 def convert_response( 

58 self, 

59 payload: RelayResponsePayload, 

60 source: RelayFormat, 

61 target: RelayFormat, 

62 *, 

63 context: RelayConversionContext | None = None, 

64 registry: RelayRegistryProtocol | None = None, 

65 ) -> Result[RelayConvertResult[RelayResponsePayload], RelayError]: 

66 """Convert a non-stream response payload from *source* to *target*.""" 

67 ... 

68 

69 def new_stream_session( 

70 self, 

71 source: RelayFormat, 

72 target: RelayFormat, 

73 *, 

74 options: RelayStreamOptions | None = None, 

75 context: RelayConversionContext | None = None, 

76 registry: RelayRegistryProtocol | None = None, 

77 ) -> Result[RelayStreamSessionProtocol, RelayError]: 

78 """Create a stateful stream session for one upstream stream.""" 

79 ... 

80 

81 def convert_stream_chunk( 

82 self, 

83 session: RelayStreamSessionProtocol, 

84 event: Any, 

85 ) -> tuple[Any, ...]: 

86 """Convert one source stream event through *session*. 

87 

88 Args: 

89 session: A session previously returned by ``new_stream_session``. 

90 event: One source wire event. 

91 

92 Returns: 

93 Zero, one, or many target wire events. 

94 

95 Raises: 

96 RelayError: Wrong source format, already finalized, or 

97 malformed event. 

98 """ 

99 ... 

100 

101 def finalize( 

102 self, 

103 session: RelayStreamSessionProtocol, 

104 ) -> tuple[Any, ...]: 

105 """Close the stream deterministically and return terminal events. 

106 

107 Args: 

108 session: A session previously returned by ``new_stream_session``. 

109 

110 Returns: 

111 Target terminal events; empty when already finalized. 

112 """ 

113 ... 

114 

115 

116@runtime_checkable 

117class RelayStreamSessionProtocol(Protocol): 

118 """One mutable upstream stream, owned by the caller. 

119 

120 The session accepts exactly one source-format event at a time, 

121 emits zero, one, or many target events, and finalizes idempotently. 

122 """ 

123 

124 def accept(self, event: Any) -> tuple[Any, ...]: 

125 """Accept one source wire event and return emitted target events. 

126 

127 Args: 

128 event: One source wire event (DTO or raw dict per mapper). 

129 

130 Returns: 

131 Zero, one, or many target wire events. 

132 

133 Raises: 

134 RelayError: Wrong source format, already finalized, or 

135 malformed event. 

136 """ 

137 ... 

138 

139 def finalize(self) -> tuple[Any, ...]: 

140 """Close the stream deterministically and return terminal events. 

141 

142 Repeated calls return an empty tuple without mutation. 

143 """ 

144 ... 

145 

146 def snapshot(self) -> Any: 

147 """Return a read-only snapshot of the session state.""" 

148 ... 

149 

150 

151@runtime_checkable 

152class RelayMapperProtocol(Protocol): 

153 """One wire format's bidirectional mapping to the canonical IR. 

154 

155 A mapper may reject a feature with a ``RelayLoss`` plus a warning, 

156 but must raise ``RelayError`` for malformed or impossible payloads. 

157 """ 

158 

159 def request_to_ir(self, payload: Any) -> Any: 

160 """Convert a source request DTO into canonical ``RelayRequest``.""" 

161 ... 

162 

163 def ir_to_request(self, request: Any) -> Any: 

164 """Convert a canonical ``RelayRequest`` into the target request DTO.""" 

165 ... 

166 

167 def response_to_ir(self, payload: Any) -> Any: 

168 """Convert a source response DTO into canonical ``RelayResponse``.""" 

169 ... 

170 

171 def ir_to_response(self, response: Any) -> Any: 

172 """Convert a canonical ``RelayResponse`` into the target response DTO.""" 

173 ... 

174 

175 def stream_to_delta(self, event: Any) -> tuple[Any, ...]: 

176 """Convert one source stream event into canonical ``StreamDelta``s.""" 

177 ... 

178 

179 def delta_to_stream(self, delta: Any) -> tuple[Any, ...]: 

180 """Convert one canonical ``StreamDelta`` into target stream events.""" 

181 ... 

182 

183 

184@runtime_checkable 

185class RelayRegistryProtocol(Protocol): 

186 """Route lookup and caller-owned mapper registration.""" 

187 

188 def mapper( 

189 self, 

190 source: RelayFormat, 

191 target: RelayFormat, 

192 ) -> RelayMapperProtocol | None: 

193 """Return the mapper for a directed pair, or ``None``. 

194 

195 Args: 

196 source: Source wire format. 

197 target: Target wire format. 

198 

199 Returns: 

200 The registered mapper, or ``None`` when the route is unknown. 

201 """ 

202 ... 

203 

204 def converter_routes(self) -> tuple[tuple[RelayFormat, RelayFormat], ...]: 

205 """Return every supported directed route pair. 

206 

207 Returns: 

208 Sorted route pairs; same-format no-op pairs are excluded. 

209 """ 

210 ... 

211 

212 def mapper_ids(self) -> tuple[str, ...]: 

213 """Return the registered mapper wire-format identifiers. 

214 

215 Returns: 

216 Sorted mapper ids, one per registered mapper. 

217 """ 

218 ... 

219 

220 def converter_version(self) -> str: 

221 """Return the converter engine version string. 

222 

223 Returns: 

224 A version string suitable for diagnostics display. 

225 """ 

226 ... 

227 

228 def route_quality( 

229 self, 

230 source: RelayFormat, 

231 target: RelayFormat, 

232 ) -> ConversionQuality: 

233 """Return the semantic-closeness quality for a directed pair. 

234 

235 Same-format pairs are always ``GOOD``; unknown routes fall back 

236 to ``DISCOURAGED`` rather than raising. 

237 

238 Args: 

239 source: Source wire format. 

240 target: Target wire format. 

241 

242 Returns: 

243 The stable quality value for the pair. 

244 """ 

245 ...