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