Skip to main content

monoloop_loop/transaction/
fake_support.rs

1//! Test encoders used by startup and exchange tests.
2
3use monoloop_contracts::{
4    Bytes, CanonicalMessage, DialectDescriptor, EncodedExchange, EncodingError,
5    ExchangeInputPolicy, InitialEncodeRequest, OutboundDialectEncoder,
6    ToolContinuationEncodeRequest,
7};
8
9/// Encoder that rejects all encode calls.
10#[derive(Debug, Default)]
11pub struct RejectEncoder;
12
13impl OutboundDialectEncoder for RejectEncoder {
14    fn encode_initial(
15        &self,
16        _request: InitialEncodeRequest<'_>,
17    ) -> Result<EncodedExchange, EncodingError> {
18        Err(EncodingError::Unsupported("reject encoder"))
19    }
20
21    fn encode_tool_continuation(
22        &self,
23        _request: ToolContinuationEncodeRequest<'_>,
24    ) -> Result<EncodedExchange, EncodingError> {
25        Err(EncodingError::Unsupported("reject encoder"))
26    }
27}
28
29/// Encoder returning empty bytes.
30#[derive(Debug)]
31pub struct EmptyBytesEncoder {
32    /// Dialect stamped on the encoded exchange.
33    pub dialect: DialectDescriptor,
34}
35
36impl EmptyBytesEncoder {
37    /// Construct with an explicit dialect stamp.
38    pub fn new(dialect: DialectDescriptor) -> Self {
39        Self { dialect }
40    }
41}
42
43impl OutboundDialectEncoder for EmptyBytesEncoder {
44    fn encode_initial(
45        &self,
46        _request: InitialEncodeRequest<'_>,
47    ) -> Result<EncodedExchange, EncodingError> {
48        Ok(EncodedExchange {
49            bytes: Bytes::new(),
50            required_input_dialect: self.dialect.clone(),
51            input_policy: ExchangeInputPolicy::SendAndFinish,
52        })
53    }
54
55    fn encode_tool_continuation(
56        &self,
57        _request: ToolContinuationEncodeRequest<'_>,
58    ) -> Result<EncodedExchange, EncodingError> {
59        Ok(EncodedExchange {
60            bytes: Bytes::new(),
61            required_input_dialect: self.dialect.clone(),
62            input_policy: ExchangeInputPolicy::SendAndFinish,
63        })
64    }
65}
66
67/// Loop-owned **smoke** encoder for FakeConnector + [`DialectDescriptor::test_raw`].
68///
69/// Joins text parts as UTF-8 and ensures a trailing sentence terminator so the
70/// segmenter emits. Not a production Channel encoder and not a testkit fixture
71/// encoder — live hosts must use profile `*_channel_binding` encoders instead.
72#[derive(Debug, Default)]
73pub struct TestTextEncoder;
74
75impl OutboundDialectEncoder for TestTextEncoder {
76    fn encode_initial(
77        &self,
78        request: InitialEncodeRequest<'_>,
79    ) -> Result<EncodedExchange, EncodingError> {
80        let mut text = String::new();
81        for msg in request.input.messages() {
82            match msg {
83                CanonicalMessage::System { content, .. }
84                | CanonicalMessage::User { content, .. }
85                | CanonicalMessage::Tool { content, .. } => {
86                    for part in content {
87                        text.push_str(part.text());
88                        text.push(' ');
89                    }
90                }
91                CanonicalMessage::Assistant { content, .. } => {
92                    for part in content {
93                        text.push_str(part.text());
94                        text.push(' ');
95                    }
96                }
97            }
98        }
99        let trimmed = text.trim();
100        if trimmed.is_empty() {
101            return Err(EncodingError::UnrepresentableInput);
102        }
103        let mut body = trimmed.to_string();
104        if !body.ends_with('.') && !body.ends_with('!') && !body.ends_with('?') {
105            body.push('.');
106        }
107        body.push(' ');
108        Ok(EncodedExchange {
109            bytes: Bytes::from(body.into_bytes()),
110            required_input_dialect: DialectDescriptor::test_raw(),
111            input_policy: ExchangeInputPolicy::SendAndFinish,
112        })
113    }
114
115    fn encode_tool_continuation(
116        &self,
117        _request: ToolContinuationEncodeRequest<'_>,
118    ) -> Result<EncodedExchange, EncodingError> {
119        Err(EncodingError::Unsupported(
120            "TestTextEncoder has no tool continuation",
121        ))
122    }
123}