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 ProxySubmit = 200,
34 ProxyCancel = 201,
35 ProxyStdoutChunk = 202,
36 ProxyStderrChunk = 203,
37 ProxyCompleted = 204,
38 ProxyRejected = 205,
39}
40
41impl SkillMessageType {
42 pub fn from_u32(v: u32) -> Option<Self> {
43 match v {
44 1 => Some(Self::Execute),
45 2 => Some(Self::Cancel),
46 3 => Some(Self::StdinData),
47 4 => Some(Self::Resize),
48 5 => Some(Self::Signal),
49 6 => Some(Self::StartSession),
50 7 => Some(Self::Shutdown),
51 128 => Some(Self::Ack),
52 129 => Some(Self::StdoutChunk),
53 130 => Some(Self::StderrChunk),
54 131 => Some(Self::Progress),
55 132 => Some(Self::Completed),
56 133 => Some(Self::Error),
57 134 => Some(Self::SessionStarted),
58 200 => Some(Self::ProxySubmit),
59 201 => Some(Self::ProxyCancel),
60 202 => Some(Self::ProxyStdoutChunk),
61 203 => Some(Self::ProxyStderrChunk),
62 204 => Some(Self::ProxyCompleted),
63 205 => Some(Self::ProxyRejected),
64 _ => None,
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct SkillMessage {
72 pub msg_type: SkillMessageType,
73 pub payload: Vec<u8>,
74}
75
76impl SkillMessage {
77 pub fn new(msg_type: SkillMessageType, payload: Vec<u8>) -> Self {
78 Self { msg_type, payload }
79 }
80
81 pub fn deserialize_payload<T: for<'de> Deserialize<'de>>(&self) -> Option<T> {
83 serde_json::from_slice(&self.payload).ok()
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
91#[serde(rename_all = "PascalCase")]
92pub struct ExecutePayload {
93 pub execution_id: String,
94 pub command_name: String,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub command: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub args: Option<Vec<String>>,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub working_directory: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub environment: Option<HashMap<String, String>>,
103 #[serde(default)]
104 pub timeout_ms: i32,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(rename_all = "PascalCase")]
110pub struct StartSessionPayload {
111 pub session_id: String,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub shell: Option<String>,
114 #[serde(default = "default_rows")]
115 pub rows: i32,
116 #[serde(default = "default_cols")]
117 pub cols: i32,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub working_directory: Option<String>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub environment: Option<HashMap<String, String>>,
122}
123
124fn default_rows() -> i32 {
125 24
126}
127fn default_cols() -> i32 {
128 80
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "PascalCase")]
134pub struct StdinDataPayload {
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub id: Option<String>,
137 #[serde(with = "base64_bytes")]
138 pub data: Vec<u8>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "PascalCase")]
144pub struct ResizePayload {
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub session_id: Option<String>,
147 pub rows: i32,
148 pub cols: i32,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153#[serde(rename_all = "PascalCase")]
154pub struct SignalPayload {
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub id: Option<String>,
157 pub signal_number: i32,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "PascalCase")]
163pub struct AckPayload {
164 pub id: String,
165 pub accepted: bool,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(rename_all = "PascalCase")]
171pub struct DataChunkPayload {
172 pub id: String,
173 pub seq: u64,
174 #[serde(with = "base64_bytes")]
175 pub data: Vec<u8>,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180#[serde(rename_all = "PascalCase")]
181pub struct ProgressPayload {
182 pub id: String,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub message: Option<String>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub percent: Option<i32>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub stage: Option<String>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193#[serde(rename_all = "PascalCase")]
194pub struct CompletedPayload {
195 pub id: String,
196 pub exit_code: i32,
197 pub status: String,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub error: Option<String>,
200 pub finished_at_unix: i64,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205#[serde(rename_all = "PascalCase")]
206pub struct ErrorPayload {
207 #[serde(skip_serializing_if = "Option::is_none")]
208 pub id: Option<String>,
209 pub code: String,
210 pub message: String,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215#[serde(rename_all = "PascalCase")]
216pub struct SessionStartedPayload {
217 pub id: String,
218 pub status: String,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(rename_all = "PascalCase")]
226pub struct ProxyTarget {
227 pub kind: String,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub agent_id: Option<String>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234#[serde(rename_all = "PascalCase")]
235pub struct ProxySubmitPayload {
236 pub proxy_id: String,
237 pub external_id: String,
238 pub command_name: String,
239 pub args: Vec<String>,
240 pub target: ProxyTarget,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245#[serde(rename_all = "PascalCase")]
246pub struct ProxyRejectedPayload {
247 pub proxy_id: String,
248 pub reason_code: String,
249 pub message: String,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(rename_all = "PascalCase")]
255pub struct ProxyChunkPayload {
256 pub proxy_id: String,
257 pub seq: u64,
258 #[serde(with = "base64_bytes")]
259 pub data: Vec<u8>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264#[serde(rename_all = "PascalCase")]
265pub struct ProxyCompletedPayload {
266 pub proxy_id: String,
267 pub exit_code: i32,
268 pub status: String,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub error: Option<String>,
271 pub finished_at_unix: i64,
272}
273
274mod base64_bytes {
276 use base64::prelude::*;
277 use serde::{self, Deserialize, Deserializer, Serializer};
278
279 pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
280 where
281 S: Serializer,
282 {
283 serializer.serialize_str(&BASE64_STANDARD.encode(bytes))
284 }
285
286 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
287 where
288 D: Deserializer<'de>,
289 {
290 let s = String::deserialize(deserializer)?;
291 BASE64_STANDARD.decode(&s).map_err(serde::de::Error::custom)
292 }
293}
294
295#[cfg(test)]
296#[path = "message_tests.rs"]
297mod tests;