1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3
4pub type JsonSchema = Map<String, Value>;
5pub type RpcArguments = Map<String, Value>;
6
7#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
8pub struct AuthContext {
9 #[serde(skip_serializing_if = "Option::is_none")]
10 pub scheme: Option<String>,
11 #[serde(skip_serializing_if = "Option::is_none")]
12 pub token: Option<String>,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub headers: Option<std::collections::HashMap<String, String>>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub metadata: Option<Map<String, Value>>,
17}
18
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct ClientInfo {
21 pub id: String,
22 pub name: String,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub description: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub version: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub platform: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub metadata: Option<Map<String, Value>>,
31}
32
33impl ClientInfo {
34 pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
35 Self {
36 id: id.into(),
37 name: name.into(),
38 description: None,
39 version: None,
40 platform: None,
41 metadata: None,
42 }
43 }
44
45 pub fn apply_override(&self, override_info: Option<ClientInfoOverride>) -> Self {
46 match override_info {
47 None => self.clone(),
48 Some(override_info) => Self {
49 id: override_info.id.unwrap_or_else(|| self.id.clone()),
50 name: override_info.name.unwrap_or_else(|| self.name.clone()),
51 description: override_info.description.or_else(|| self.description.clone()),
52 version: override_info.version.or_else(|| self.version.clone()),
53 platform: override_info.platform.or_else(|| self.platform.clone()),
54 metadata: override_info.metadata.or_else(|| self.metadata.clone()),
55 },
56 }
57 }
58}
59
60#[derive(Clone, Debug, Default)]
61pub struct ClientInfoOverride {
62 pub id: Option<String>,
63 pub name: Option<String>,
64 pub description: Option<String>,
65 pub version: Option<String>,
66 pub platform: Option<String>,
67 pub metadata: Option<Map<String, Value>>,
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
71pub enum HttpMethod {
72 #[serde(rename = "GET")]
73 Get,
74 #[serde(rename = "POST")]
75 Post,
76 #[serde(rename = "PUT")]
77 Put,
78 #[serde(rename = "PATCH")]
79 Patch,
80 #[serde(rename = "DELETE")]
81 Delete,
82}
83
84impl HttpMethod {
85 pub fn as_str(&self) -> &'static str {
86 match self {
87 Self::Get => "GET",
88 Self::Post => "POST",
89 Self::Put => "PUT",
90 Self::Patch => "PATCH",
91 Self::Delete => "DELETE",
92 }
93 }
94}
95
96#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
97#[serde(tag = "type")]
98pub enum PathDescriptor {
99 #[serde(rename = "endpoint")]
100 Endpoint {
101 path: String,
102 method: HttpMethod,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 description: Option<String>,
105 #[serde(rename = "inputSchema", skip_serializing_if = "Option::is_none")]
106 input_schema: Option<JsonSchema>,
107 #[serde(rename = "outputSchema", skip_serializing_if = "Option::is_none")]
108 output_schema: Option<JsonSchema>,
109 #[serde(rename = "contentType", skip_serializing_if = "Option::is_none")]
110 content_type: Option<String>,
111 },
112 #[serde(rename = "skill")]
113 Skill {
114 path: String,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 description: Option<String>,
117 #[serde(rename = "contentType")]
118 content_type: String,
119 },
120 #[serde(rename = "prompt")]
121 Prompt {
122 path: String,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 description: Option<String>,
125 #[serde(rename = "inputSchema", skip_serializing_if = "Option::is_none")]
126 input_schema: Option<JsonSchema>,
127 #[serde(rename = "outputSchema", skip_serializing_if = "Option::is_none")]
128 output_schema: Option<JsonSchema>,
129 },
130}
131
132impl PathDescriptor {
133 pub fn path(&self) -> &str {
134 match self {
135 Self::Endpoint { path, .. } | Self::Skill { path, .. } | Self::Prompt { path, .. } => path,
136 }
137 }
138
139 pub fn descriptor_type(&self) -> &'static str {
140 match self {
141 Self::Endpoint { .. } => "endpoint",
142 Self::Skill { .. } => "skill",
143 Self::Prompt { .. } => "prompt",
144 }
145 }
146}
147
148#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
149pub struct ClientDescriptor {
150 pub id: String,
151 pub name: String,
152 pub paths: Vec<PathDescriptor>,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub description: Option<String>,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub version: Option<String>,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub platform: Option<String>,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub metadata: Option<Map<String, Value>>,
161}
162
163impl ClientDescriptor {
164 pub fn from_info(info: &ClientInfo, paths: Vec<PathDescriptor>) -> Self {
165 Self {
166 id: info.id.clone(),
167 name: info.name.clone(),
168 description: info.description.clone(),
169 version: info.version.clone(),
170 platform: info.platform.clone(),
171 metadata: info.metadata.clone(),
172 paths,
173 }
174 }
175}
176
177#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
178pub struct SerializedError {
179 pub code: String,
180 pub message: String,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub details: Option<Value>,
183}
184
185impl SerializedError {
186 pub fn handler(message: impl Into<String>) -> Self {
187 Self {
188 code: "handler_error".to_string(),
189 message: message.into(),
190 details: None,
191 }
192 }
193}
194
195#[derive(Clone, Debug, PartialEq)]
196pub struct PathRequest {
197 pub params: RpcArguments,
198 pub queries: RpcArguments,
199 pub body: Option<Value>,
200 pub headers: std::collections::HashMap<String, String>,
201}
202
203#[derive(Clone, Debug, PartialEq)]
204pub struct PathInvocationContext {
205 pub request_id: String,
206 pub client_id: String,
207 pub path_type: String,
208 pub method: HttpMethod,
209 pub path: String,
210 pub auth: Option<AuthContext>,
211}
212
213#[derive(Clone, Debug, Default)]
214pub struct EndpointOptions {
215 pub description: Option<String>,
216 pub input_schema: Option<JsonSchema>,
217 pub output_schema: Option<JsonSchema>,
218 pub content_type: Option<String>,
219}
220
221impl EndpointOptions {
222 pub fn new() -> Self {
223 Self::default()
224 }
225
226 pub fn description(mut self, description: impl Into<String>) -> Self {
227 self.description = Some(description.into());
228 self
229 }
230}
231
232#[derive(Clone, Debug)]
233pub struct SkillOptions {
234 pub description: Option<String>,
235 pub content_type: String,
236}
237
238impl Default for SkillOptions {
239 fn default() -> Self {
240 Self {
241 description: None,
242 content_type: "text/markdown".to_string(),
243 }
244 }
245}
246
247impl SkillOptions {
248 pub fn new() -> Self {
249 Self::default()
250 }
251
252 pub fn description(mut self, description: impl Into<String>) -> Self {
253 self.description = Some(description.into());
254 self
255 }
256}
257
258#[derive(Clone, Debug, Default)]
259pub struct PromptOptions {
260 pub description: Option<String>,
261 pub input_schema: Option<JsonSchema>,
262 pub output_schema: Option<JsonSchema>,
263}
264
265impl PromptOptions {
266 pub fn new() -> Self {
267 Self::default()
268 }
269
270 pub fn description(mut self, description: impl Into<String>) -> Self {
271 self.description = Some(description.into());
272 self
273 }
274}