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