lash_remote_protocol/
turn_input.rs1use 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}