Skip to main content

monoloop_contracts/
encoder.rs

1//! Outbound dialect encoder contracts (provider-neutral request/result types).
2
3use crate::config::EffectiveConfig;
4use crate::dialect::DialectDescriptor;
5use crate::id::{ExchangeId, TransactionId};
6use crate::input::CanonicalInput;
7use crate::input::CanonicalMessage;
8use crate::tool::{CanonicalToolResult, ToolSpec};
9use bytes::Bytes;
10use thiserror::Error;
11
12/// Whether the encoded exchange finishes the connection write path.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum ExchangeInputPolicy {
15    /// Send body and finish the request (HTTP request/response).
16    SendAndFinish,
17    /// Send while retaining the bidirectional session.
18    SendAndRetain,
19}
20
21/// Encoded provider exchange body.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct EncodedExchange {
24    /// Bounded dialect bytes.
25    pub bytes: Bytes,
26    /// Dialect the Connector must accept for this write.
27    pub required_input_dialect: DialectDescriptor,
28    /// Send-and-finish vs retain.
29    pub input_policy: ExchangeInputPolicy,
30}
31
32/// Initial outbound encode request (no live handler objects).
33#[derive(Clone, Debug)]
34pub struct InitialEncodeRequest<'a> {
35    /// Transaction id.
36    pub transaction_id: &'a TransactionId,
37    /// Exchange id.
38    pub exchange_id: &'a ExchangeId,
39    /// Canonical caller input.
40    pub input: &'a CanonicalInput,
41    /// Effective configuration.
42    pub config: &'a EffectiveConfig,
43    /// Ordered tool specs for this transaction.
44    pub tools: &'a [ToolSpec],
45}
46
47/// Immutable continuation context: original input plus required tool turns.
48#[derive(Clone, Debug, PartialEq)]
49pub struct ContinuationContext {
50    messages: Vec<CanonicalMessage>,
51}
52
53impl ContinuationContext {
54    /// Construct from a mechanical message sequence (caller/runtime-built).
55    pub fn try_new(messages: Vec<CanonicalMessage>) -> Result<Self, EncodingError> {
56        if messages.is_empty() {
57            return Err(EncodingError::EmptyContinuationContext);
58        }
59        Ok(Self { messages })
60    }
61
62    /// Borrow messages.
63    pub fn messages(&self) -> &[CanonicalMessage] {
64        &self.messages
65    }
66}
67
68/// Tool-result continuation encode request.
69#[derive(Clone, Debug)]
70pub struct ToolContinuationEncodeRequest<'a> {
71    /// Transaction id.
72    pub transaction_id: &'a TransactionId,
73    /// Exchange id.
74    pub exchange_id: &'a ExchangeId,
75    /// Continuation context.
76    pub context: &'a ContinuationContext,
77    /// Canonical tool results for this continuation.
78    pub results: &'a [CanonicalToolResult],
79    /// Effective configuration.
80    pub config: &'a EffectiveConfig,
81    /// Ordered tool specs.
82    pub tools: &'a [ToolSpec],
83}
84
85/// Outbound dialect encoder port (implemented in monoloop-loop adapters).
86pub trait OutboundDialectEncoder: Send + Sync {
87    /// Encode the first provider exchange for a transaction.
88    fn encode_initial(
89        &self,
90        request: InitialEncodeRequest<'_>,
91    ) -> Result<EncodedExchange, EncodingError>;
92
93    /// Encode a tool-result continuation exchange.
94    fn encode_tool_continuation(
95        &self,
96        request: ToolContinuationEncodeRequest<'_>,
97    ) -> Result<EncodedExchange, EncodingError>;
98}
99
100/// Encoding failure (maps to `EncodingFailed` terminal).
101#[derive(Clone, Debug, Error, PartialEq, Eq)]
102pub enum EncodingError {
103    /// Empty continuation context.
104    #[error("continuation context must be non-empty")]
105    EmptyContinuationContext,
106    /// Unsupported dialect or option.
107    #[error("unsupported encode option: {0}")]
108    Unsupported(&'static str),
109    /// Output would exceed Channel/runtime byte bound.
110    #[error("encoded exchange exceeds bound")]
111    LimitExceeded,
112    /// Invalid configuration for this dialect.
113    #[error("invalid configuration for encoder")]
114    InvalidConfiguration,
115    /// Input cannot be represented in this dialect.
116    #[error("input not representable in dialect")]
117    UnrepresentableInput,
118}