Coverage for src / lexigram / ai / relay / mappers / base.py: 100%
26 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
1"""Typed per-format mapper interfaces for the relay conversion engine.
3Each wire format implements one :class:`FormatMapper` with the six
4canonical operations: request/response in both directions plus
5stream conversion in both directions. Mappers may reject a feature by
6recording a :class:`RelayLoss` and a warning, but must return
7``Err(RelayError)`` for malformed or impossible payloads.
8"""
10from __future__ import annotations
12from collections.abc import Sequence
13from typing import Any, Protocol, runtime_checkable
14from uuid import uuid4
16from lexigram.ai.relay.context import ConversionContext
17from lexigram.contracts.ai.exceptions import RelayError
18from lexigram.contracts.ai.relay.ir import (
19 RelayRequest,
20 RelayResponse,
21 StreamDelta,
22 StreamState,
23)
24from lexigram.contracts.ai.relay.types import RelayFormat, RelayLoss
25from lexigram.contracts.core.result import Result
27__all__ = ["FormatMapper", "record_loss", "warning_messages"]
30@runtime_checkable
31class FormatMapper(Protocol):
32 """One wire format's typed bidirectional mapping to the canonical IR.
34 Concrete mappers implement the same operations the container-facing
35 ``RelayMapperProtocol`` declares; the registry in the relay engine
36 holds :class:`FormatMapper` implementations.
37 """
39 @property
40 def format(self) -> RelayFormat:
41 """The wire format this mapper serves; concrete mappers define it."""
42 ...
44 def request_to_ir(
45 self, payload: Any, *, context: ConversionContext
46 ) -> Result[RelayRequest, RelayError]:
47 """Convert a source request DTO into canonical ``RelayRequest``."""
48 ...
50 def ir_to_request(
51 self, request: RelayRequest, *, context: ConversionContext
52 ) -> Result[Any, RelayError]:
53 """Convert a canonical ``RelayRequest`` into the target request DTO."""
54 ...
56 def response_to_ir(
57 self, payload: Any, *, context: ConversionContext
58 ) -> Result[RelayResponse, RelayError]:
59 """Convert a source response DTO into canonical ``RelayResponse``."""
60 ...
62 def ir_to_response(
63 self, response: RelayResponse, *, context: ConversionContext
64 ) -> Result[Any, RelayError]:
65 """Convert a canonical ``RelayResponse`` into the target response DTO."""
66 ...
68 def stream_to_delta(
69 self, event: Any, *, state: StreamState
70 ) -> Result[tuple[StreamDelta, ...], RelayError]:
71 """Convert one source stream event into canonical ``StreamDelta``s."""
72 ...
74 def delta_to_stream(
75 self, delta: StreamDelta, *, state: StreamState
76 ) -> Result[tuple[Any, ...], RelayError]:
77 """Convert one canonical ``StreamDelta`` into target stream events."""
78 ...
81def record_loss(
82 context: ConversionContext,
83 *,
84 field: str,
85 target: RelayFormat,
86 reason: str,
87 severity: str = "warning",
88) -> None:
89 """Record a semantic loss on the conversion context.
91 The engine copies the accumulated losses into the
92 ``RelayConvertResult`` and surfaces ``warning``/``error`` losses as
93 warnings.
95 Args:
96 context: The per-conversion context holding the loss sink.
97 field: Source wire field (or feature) that was dropped or adapted.
98 target: Target format the loss applies to.
99 reason: Machine-readable reason (e.g. ``json_mode_not_supported``).
100 severity: ``error``, ``warning``, or ``info``.
101 """
102 context.losses.append(
103 RelayLoss(field=field, target=target, reason=reason, severity=severity)
104 )
107def warning_messages(losses: Sequence[RelayLoss]) -> tuple[str, ...]:
108 """Render losses into stable warning strings.
110 Args:
111 losses: Loss records accumulated during conversion.
113 Returns:
114 One ``"field: reason (target, severity)"`` string per loss.
115 """
116 return tuple(
117 f"{loss.field}: {loss.reason} ({loss.target.value}, {loss.severity})"
118 for loss in losses
119 )
122def new_uuid() -> str:
123 """Return a fresh 32-hex identifier (relaykit ``GetUUID`` shape)."""
124 return uuid4().hex