Skip to main content

lash_remote_protocol/
turn_input.rs

1//! Turn input envelopes: items, image blobs, per-turn protocol options, and
2//! the turn request.
3
4use std::collections::HashMap;
5
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9use crate::llm::RemoteModelIntent;
10use crate::prompt::RemotePromptLayer;
11use crate::registry_errors::{RemoteProtocolError, require_non_empty};
12use crate::tools::RemoteToolGrant;
13use crate::{REMOTE_PROTOCOL_VERSION, ensure_protocol_version};
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
16pub struct RemoteProtocolTurnOptions {
17    #[serde(default = "empty_protocol_turn_payload")]
18    pub payload: serde_json::Value,
19}
20
21fn empty_protocol_turn_payload() -> serde_json::Value {
22    serde_json::Value::Object(serde_json::Map::new())
23}
24
25impl Default for RemoteProtocolTurnOptions {
26    fn default() -> Self {
27        Self {
28            payload: empty_protocol_turn_payload(),
29        }
30    }
31}
32
33impl RemoteProtocolTurnOptions {
34    pub fn empty() -> Self {
35        Self::default()
36    }
37
38    pub fn is_empty(&self) -> bool {
39        match &self.payload {
40            serde_json::Value::Object(map) => map.is_empty(),
41            _ => false,
42        }
43    }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
47pub struct RemoteTurnInput {
48    pub protocol_version: u32,
49    #[serde(default)]
50    pub items: Vec<RemoteInputItem>,
51    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
52    pub image_blobs_base64: HashMap<String, String>,
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub protocol_turn_options: Option<RemoteProtocolTurnOptions>,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub trace_turn_id: Option<String>,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub prompt_layer: Option<RemotePromptLayer>,
59}
60
61impl RemoteTurnInput {
62    pub fn text(text: impl Into<String>) -> Self {
63        Self {
64            protocol_version: REMOTE_PROTOCOL_VERSION,
65            items: vec![RemoteInputItem::Text { text: text.into() }],
66            image_blobs_base64: HashMap::new(),
67            protocol_turn_options: None,
68            trace_turn_id: None,
69            prompt_layer: None,
70        }
71    }
72
73    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
74        ensure_protocol_version(self.protocol_version)?;
75        for item in &self.items {
76            if let RemoteInputItem::ImageRef { id } = item {
77                require_non_empty("RemoteInputItem::ImageRef", "id", id)?;
78            }
79        }
80        for (id, blob) in &self.image_blobs_base64 {
81            require_non_empty("RemoteTurnInput", "image_blobs_base64 key", id)?;
82            if blob.trim().is_empty() {
83                return Err(RemoteProtocolError::InvalidImageBlob {
84                    id: id.clone(),
85                    message: "base64 payload cannot be empty".to_string(),
86                });
87            }
88        }
89        Ok(())
90    }
91}
92
93#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
94#[serde(tag = "type", rename_all = "snake_case")]
95pub enum RemoteInputItem {
96    Text { text: String },
97    ImageRef { id: String },
98}
99
100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
101pub struct RemoteTurnRequest {
102    pub protocol_version: u32,
103    pub session_id: String,
104    pub turn_id: String,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub idempotency_key: Option<String>,
107    pub input: RemoteTurnInput,
108    #[serde(default, skip_serializing_if = "Vec::is_empty")]
109    pub tool_grants: Vec<RemoteToolGrant>,
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub model_intent: Option<RemoteModelIntent>,
112    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
113    pub metadata: HashMap<String, serde_json::Value>,
114}
115
116impl RemoteTurnRequest {
117    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
118        ensure_protocol_version(self.protocol_version)?;
119        require_non_empty("RemoteTurnRequest", "session_id", &self.session_id)?;
120        require_non_empty("RemoteTurnRequest", "turn_id", &self.turn_id)?;
121        if self.input.protocol_version != self.protocol_version {
122            return Err(RemoteProtocolError::MismatchedNestedProtocolVersion {
123                parent: "RemoteTurnRequest",
124                child: "input",
125                parent_version: self.protocol_version,
126                child_version: self.input.protocol_version,
127            });
128        }
129        self.input.validate()?;
130        RemoteToolGrant::validate_all(&self.tool_grants)?;
131        if let Some(model_intent) = &self.model_intent {
132            model_intent.validate()?;
133        }
134        Ok(())
135    }
136}