1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u32)]
13pub enum SkillMessageType {
14 Execute = 1,
16 Cancel = 2,
17 StdinData = 3,
18 Resize = 4,
19 Signal = 5,
20 StartSession = 6,
21 Shutdown = 7,
22
23 Ack = 128,
25 StdoutChunk = 129,
26 StderrChunk = 130,
27 Progress = 131,
28 Completed = 132,
29 Error = 133,
30 SessionStarted = 134,
31}
32
33impl SkillMessageType {
34 pub fn from_u32(v: u32) -> Option<Self> {
35 match v {
36 1 => Some(Self::Execute),
37 2 => Some(Self::Cancel),
38 3 => Some(Self::StdinData),
39 4 => Some(Self::Resize),
40 5 => Some(Self::Signal),
41 6 => Some(Self::StartSession),
42 7 => Some(Self::Shutdown),
43 128 => Some(Self::Ack),
44 129 => Some(Self::StdoutChunk),
45 130 => Some(Self::StderrChunk),
46 131 => Some(Self::Progress),
47 132 => Some(Self::Completed),
48 133 => Some(Self::Error),
49 134 => Some(Self::SessionStarted),
50 _ => None,
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct SkillMessage {
58 pub msg_type: SkillMessageType,
59 pub payload: Vec<u8>,
60}
61
62impl SkillMessage {
63 pub fn new(msg_type: SkillMessageType, payload: Vec<u8>) -> Self {
64 Self { msg_type, payload }
65 }
66
67 pub fn deserialize_payload<T: for<'de> Deserialize<'de>>(&self) -> Option<T> {
69 serde_json::from_slice(&self.payload).ok()
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(rename_all = "PascalCase")]
78pub struct ExecutePayload {
79 pub execution_id: String,
80 pub command_name: String,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub command: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub args: Option<Vec<String>>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub working_directory: Option<String>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub environment: Option<HashMap<String, String>>,
89 #[serde(default)]
90 pub timeout_ms: i32,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(rename_all = "PascalCase")]
96pub struct StartSessionPayload {
97 pub session_id: String,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub shell: Option<String>,
100 #[serde(default = "default_rows")]
101 pub rows: i32,
102 #[serde(default = "default_cols")]
103 pub cols: i32,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub working_directory: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub environment: Option<HashMap<String, String>>,
108}
109
110fn default_rows() -> i32 {
111 24
112}
113fn default_cols() -> i32 {
114 80
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119#[serde(rename_all = "PascalCase")]
120pub struct StdinDataPayload {
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub id: Option<String>,
123 #[serde(with = "base64_bytes")]
124 pub data: Vec<u8>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "PascalCase")]
130pub struct ResizePayload {
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub session_id: Option<String>,
133 pub rows: i32,
134 pub cols: i32,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139#[serde(rename_all = "PascalCase")]
140pub struct SignalPayload {
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub id: Option<String>,
143 pub signal_number: i32,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(rename_all = "PascalCase")]
149pub struct AckPayload {
150 pub id: String,
151 pub accepted: bool,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(rename_all = "PascalCase")]
157pub struct DataChunkPayload {
158 pub id: String,
159 pub seq: u64,
160 #[serde(with = "base64_bytes")]
161 pub data: Vec<u8>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166#[serde(rename_all = "PascalCase")]
167pub struct ProgressPayload {
168 pub id: String,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub message: Option<String>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub percent: Option<i32>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub stage: Option<String>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(rename_all = "PascalCase")]
180pub struct CompletedPayload {
181 pub id: String,
182 pub exit_code: i32,
183 pub status: String,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub error: Option<String>,
186 pub finished_at_unix: i64,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(rename_all = "PascalCase")]
192pub struct ErrorPayload {
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub id: Option<String>,
195 pub code: String,
196 pub message: String,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201#[serde(rename_all = "PascalCase")]
202pub struct SessionStartedPayload {
203 pub id: String,
204 pub status: String,
205}
206
207mod base64_bytes {
210 use base64::prelude::*;
211 use serde::{self, Deserialize, Deserializer, Serializer};
212
213 pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
214 where
215 S: Serializer,
216 {
217 serializer.serialize_str(&BASE64_STANDARD.encode(bytes))
218 }
219
220 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
221 where
222 D: Deserializer<'de>,
223 {
224 let s = String::deserialize(deserializer)?;
225 BASE64_STANDARD.decode(&s).map_err(serde::de::Error::custom)
226 }
227}
228
229#[cfg(test)]
230#[path = "message_tests.rs"]
231mod tests;