1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use std::future::Future;
4use std::panic::AssertUnwindSafe;
5
6strkind::strkind! {
9 pub ThreadId;
17
18 pub ToolCallId;
26
27 pub ProviderCallId;
32}
33
34pub type ChoiceId<T = String> = ToolCallId<T>;
41
42#[derive(Debug, Serialize, Deserialize)]
45pub struct RapInvocation {
46 pub operation: String,
47 #[serde(default)]
48 pub arguments: serde_json::Value,
49 pub id: ToolCallId,
50 pub call_id: Option<ProviderCallId>,
51 pub callback_url: String,
52 pub group_id: ThreadId,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub user_id: Option<String>,
55 #[serde(default)]
57 pub thread_ancestors: Option<Vec<ThreadId>>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(tag = "type", content = "content")]
63pub enum DisplaySegment {
64 #[serde(rename = "text")]
66 Text(String),
67 #[serde(rename = "diff")]
69 Diff(DiffContent),
70 #[serde(rename = "image")]
74 Image(ImageContent),
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct DiffContent {
80 pub path: String,
82 pub patch: String,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ImageContent {
89 pub data: String,
91 #[serde(rename = "mediaType")]
93 pub media_type: String,
94}
95
96pub fn build_display_segments(
99 display_as: Option<&[DisplaySegment]>,
100 text: &str,
101) -> Vec<DisplaySegment> {
102 let mut segments: Vec<DisplaySegment> = display_as.map(|s| s.to_vec()).unwrap_or_default();
103 segments.push(DisplaySegment::Text(text.to_owned()));
104 segments
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(tag = "type")]
114pub enum RapToolResultContent {
115 #[serde(rename = "text")]
117 Text { text: String },
118 #[serde(rename = "image")]
120 Image {
121 data: String,
123 #[serde(rename = "mediaType")]
125 media_type: String,
126 },
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct RapToolResult {
131 pub group_id: ThreadId,
132 pub id: ToolCallId,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub call_id: Option<ProviderCallId>,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub text: Option<String>,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub content: Option<Vec<RapToolResultContent>>,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub display_as: Option<Vec<DisplaySegment>>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub subscription: Option<bool>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct RapUserChoice {
154 pub group_id: ThreadId,
155 pub id: ChoiceId,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub call_id: Option<ProviderCallId>,
158 pub prompt: String,
159 pub choices: Vec<String>,
160 #[serde(default)]
161 pub default: usize,
162 pub response_url: String,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct RapSubscriptionEvent {
167 pub group_id: ThreadId,
168 pub tool_call_id: ToolCallId,
169 pub text: String,
170 #[serde(default)]
171 pub associative: bool,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub r#final: Option<bool>,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct RapViewUpdate {
180 pub group_id: ThreadId,
181 pub view_type: String,
183 pub content: serde_json::Value,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct RapOAuth {
189 pub group_id: ThreadId,
190 pub id: ToolCallId,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub call_id: Option<ProviderCallId>,
193 pub auth_url: String,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198#[serde(tag = "type")]
199pub enum RapCallback {
200 #[serde(rename = "tool_result")]
201 ToolResult(RapToolResult),
202 #[serde(rename = "subscription_event")]
203 SubscriptionEvent(RapSubscriptionEvent),
204 #[serde(rename = "oauth")]
205 OAuth(RapOAuth),
206 #[serde(rename = "user_choice")]
207 UserChoice(RapUserChoice),
208 #[serde(rename = "view_update")]
209 ViewUpdate(RapViewUpdate),
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct ToolsetManifest {
214 pub name: String,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub description: Option<String>,
217 pub endpoint: String,
218 pub tools: Vec<ToolDef>,
219 #[serde(default, rename = "needsMigration")]
220 pub needs_migration: bool,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct ToolDef {
225 pub name: String,
226 pub description: String,
227 #[serde(rename = "inputSchema")]
228 pub input_schema: serde_json::Value,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
230 pub annotations: Option<serde_json::Value>,
231 #[serde(
232 default,
233 rename = "displayScript",
234 skip_serializing_if = "Option::is_none"
235 )]
236 pub display_script: Option<String>,
237}
238
239#[async_trait]
243pub trait CallbackClient: Send + Sync + 'static {
244 async fn post_json(
245 &self,
246 url: &str,
247 body: &str,
248 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
249}
250
251pub struct PlainCallbackClient {
253 client: reqwest::Client,
254}
255
256impl Default for PlainCallbackClient {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262impl PlainCallbackClient {
263 pub fn new() -> Self {
264 Self {
265 client: reqwest::Client::new(),
266 }
267 }
268}
269
270#[async_trait]
271impl CallbackClient for PlainCallbackClient {
272 async fn post_json(
273 &self,
274 url: &str,
275 body: &str,
276 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
277 self.client
278 .post(url)
279 .header("content-type", "application/json")
280 .body(body.to_owned())
281 .send()
282 .await?;
283 Ok(())
284 }
285}
286
287pub async fn send_tool_result<C: CallbackClient>(
291 client: &C,
292 invocation: &RapInvocation,
293 text: &str,
294 display_as: Option<Vec<DisplaySegment>>,
295 subscription: bool,
296) {
297 let result = RapCallback::ToolResult(RapToolResult {
298 group_id: invocation.group_id.clone(),
299 id: invocation.id.clone(),
300 call_id: invocation.call_id.clone(),
301 text: Some(text.to_owned()),
302 content: None,
303 display_as,
304 subscription: if subscription { Some(true) } else { None },
305 });
306 let body = serde_json::to_string(&result).expect("bug: failed to serialize tool result");
307 if let Err(e) = client.post_json(&invocation.callback_url, &body).await {
308 tracing::error!("failed to send tool result: {e}");
309 }
310}
311
312pub async fn send_user_choice<C: CallbackClient>(
314 client: &C,
315 invocation: &RapInvocation,
316 prompt: &str,
317 choices: Vec<String>,
318 default: usize,
319 response_url: &str,
320) {
321 let msg = RapCallback::UserChoice(RapUserChoice {
322 group_id: invocation.group_id.clone(),
323 id: invocation.id.clone(),
324 call_id: invocation.call_id.clone(),
325 prompt: prompt.to_owned(),
326 choices,
327 default,
328 response_url: response_url.to_owned(),
329 });
330 let body = serde_json::to_string(&msg).expect("bug: failed to serialize user_choice");
331 if let Err(e) = client.post_json(&invocation.callback_url, &body).await {
332 tracing::error!("failed to send user_choice: {e}");
333 }
334}
335
336pub async fn send_subscription_event<C: CallbackClient>(
338 client: &C,
339 callback_url: &str,
340 group_id: ThreadId,
341 tool_call_id: ToolCallId,
342 text: &str,
343 associative: bool,
344 r#final: bool,
345) {
346 let event = RapCallback::SubscriptionEvent(RapSubscriptionEvent {
347 group_id,
348 tool_call_id,
349 text: text.to_owned(),
350 associative,
351 r#final: if r#final { Some(true) } else { None },
352 });
353 let body = serde_json::to_string(&event).expect("bug: failed to serialize subscription event");
354 if let Err(e) = client.post_json(callback_url, &body).await {
355 tracing::error!("failed to send subscription event: {e}");
356 }
357}
358
359pub async fn send_view_update<C: CallbackClient>(
361 client: &C,
362 callback_url: &str,
363 group_id: ThreadId,
364 view_type: &str,
365 content: serde_json::Value,
366) {
367 let msg = RapCallback::ViewUpdate(RapViewUpdate {
368 group_id,
369 view_type: view_type.to_owned(),
370 content,
371 });
372 let body = serde_json::to_string(&msg).expect("bug: failed to serialize view_update");
373 if let Err(e) = client.post_json(callback_url, &body).await {
374 tracing::error!("failed to send view_update: {e}");
375 }
376}
377
378pub async fn log_panic<F: Future<Output = ()>>(name: &'static str, f: F) {
381 use futures_util::FutureExt;
382 if let Err(e) = AssertUnwindSafe(f).catch_unwind().await {
383 let msg = if let Some(s) = e.downcast_ref::<&str>() {
384 *s
385 } else if let Some(s) = e.downcast_ref::<String>() {
386 &**s
387 } else {
388 "unknown panic payload"
389 };
390 tracing::error!("task '{name}' panicked: {msg}");
391 }
392}