1use serde_json::Value;
2
3use crate::error::{map_manager_err, ManagerError};
4use crate::gen::manager::apis::configuration::Configuration;
5use crate::gen::manager::apis::{application_api as api, local_automation_api, manager_api};
6use crate::gen::manager::models;
7use crate::http::{collect_all, fetch_page, Page};
8use crate::resources::raw::{get_json, post_json, post_json_body, put_json};
9use crate::retry::{with_retry, RetryPolicy};
10use crate::token::SharedCfg;
11
12#[derive(Debug, Clone, PartialEq)]
19#[non_exhaustive]
20pub enum ApplicationItem {
21 Typed(models::Application),
23 Unknown {
25 module: String,
27 raw: serde_json::Value,
29 },
30}
31
32impl ApplicationItem {
33 pub fn as_typed(&self) -> Option<&models::Application> {
35 match self {
36 ApplicationItem::Typed(app) => Some(app),
37 ApplicationItem::Unknown { .. } => None,
38 }
39 }
40
41 pub fn module(&self) -> &str {
43 use models::Application as App;
44 match self {
45 ApplicationItem::Typed(app) => match app {
46 App::AgentQueue(_) => "agentQueue",
47 App::Agentic(_) => "agentic",
48 App::AudioPlayer(_) => "audioPlayer",
49 App::ConsumerQueue(_) => "consumerQueue",
50 App::InputReader(_) => "inputReader",
51 App::InputReaderV2(_) => "inputReader.v2",
52 App::PromptPlayer(_) => "promptPlayer",
53 App::Recording(_) => "recording",
54 App::SimpleMenu(_) => "simpleMenu",
55 App::SpeechToText(_) => "speechToText",
56 App::SwitchNode(_) => "switchNode",
57 App::TextToSpeech(_) => "textToSpeech",
58 App::Transfer(_) => "transfer",
59 },
60 ApplicationItem::Unknown { module, .. } => module,
61 }
62 }
63}
64
65fn decode_application(v: Value) -> Result<ApplicationItem, ManagerError> {
75 use models::Application as App;
76 fn var<T: serde::de::DeserializeOwned>(v: Value) -> Result<Box<T>, ManagerError> {
77 serde_json::from_value(v)
78 .map(Box::new)
79 .map_err(|e| ManagerError::Decode(e.to_string()))
80 }
81 if !v.is_object() {
82 return Err(ManagerError::Decode(
83 "application item is not a JSON object".into(),
84 ));
85 }
86 let module = v
87 .get("module")
88 .and_then(Value::as_str)
89 .unwrap_or_default()
90 .to_string();
91 Ok(ApplicationItem::Typed(match module.as_str() {
92 "agentQueue" => App::AgentQueue(var(v)?),
93 "agentic" => App::Agentic(var(v)?),
94 "audioPlayer" => App::AudioPlayer(var(v)?),
95 "consumerQueue" => App::ConsumerQueue(var(v)?),
96 "inputReader" => App::InputReader(var(v)?),
97 "inputReader.v2" => App::InputReaderV2(var(v)?),
98 "promptPlayer" => App::PromptPlayer(var(v)?),
99 "recording" => App::Recording(var(v)?),
100 "simpleMenu" => App::SimpleMenu(var(v)?),
101 "speechToText" => App::SpeechToText(var(v)?),
102 "switchNode" => App::SwitchNode(var(v)?),
103 "textToSpeech" => App::TextToSpeech(var(v)?),
104 "transfer" => App::Transfer(var(v)?),
105 _ => return Ok(ApplicationItem::Unknown { module, raw: v }),
106 }))
107}
108
109fn decode_item_envelope(v: Value) -> Result<ApplicationItem, ManagerError> {
112 let item = v
113 .get("item")
114 .cloned()
115 .ok_or_else(|| ManagerError::Decode("response had no `item`".into()))?;
116 decode_application(item)
117}
118
119async fn applications_page(
125 cfg: &Configuration,
126 page: i32,
127 per_page: Option<i32>,
128) -> Result<(Vec<ApplicationItem>, i32, i32, Option<i32>), ManagerError> {
129 let mut path = format!("/api/v2/applications?page={page}");
130 if let Some(max) = per_page {
131 path.push_str(&format!("&max={max}"));
132 }
133 let v = get_json(cfg, &path).await?;
134 let items = v
135 .get("items")
136 .and_then(Value::as_array)
137 .cloned()
138 .unwrap_or_default()
139 .into_iter()
140 .map(decode_application)
141 .collect::<Result<Vec<_>, _>>()?;
142 let p = v.get("pagination").cloned().unwrap_or(Value::Null);
143 let num = |k: &str| p.get(k).and_then(Value::as_i64).map(|n| n as i32);
144 Ok((
145 items,
146 num("pages").unwrap_or(1),
147 num("current").unwrap_or(page),
148 num("total"),
149 ))
150}
151
152pub struct ApplicationsResource {
154 pub(crate) cfg: SharedCfg<Configuration>,
155 pub(crate) retry: RetryPolicy,
156 pub local_automations: LocalAutomationsResource,
158}
159
160impl ApplicationsResource {
161 pub async fn list_all(&self) -> Result<Vec<ApplicationItem>, ManagerError> {
163 let cfg = self.cfg.get().await?;
164 let cfg = cfg.as_ref();
165 collect_all(
166 &self.retry,
167 |e| e,
168 |page| async move {
169 applications_page(cfg, page, None)
170 .await
171 .map(|(items, pages, current, _)| (items, pages, current))
172 },
173 )
174 .await
175 }
176
177 pub async fn list_page(
179 &self,
180 page: i32,
181 per_page: Option<i32>,
182 ) -> Result<Page<ApplicationItem>, ManagerError> {
183 let cfg = self.cfg.get().await?;
184 let cfg = cfg.as_ref();
185 fetch_page(
186 &self.retry,
187 |e| e,
188 || async move { applications_page(cfg, page, per_page).await },
189 )
190 .await
191 }
192
193 pub async fn all_local_automations(
195 &self,
196 ) -> Result<Vec<models::LocalAutomation>, ManagerError> {
197 let cfg = self.cfg.get().await?;
198 let cfg = cfg.as_ref();
199 collect_all(&self.retry, map_manager_err, |page| async move {
200 let r = local_automation_api::list_all_local_automations(cfg, Some(page), None).await?;
201 Ok((r.items, r.pagination.pages, r.pagination.current))
202 })
203 .await
204 }
205
206 pub async fn create(
213 &self,
214 body: models::ApplicationCreateBody,
215 ) -> Result<ApplicationItem, ManagerError> {
216 let body = serde_json::to_value(&body)
217 .map_err(|e| ManagerError::InvalidArgument(e.to_string()))?;
218 decode_item_envelope(self.create_raw(body).await?)
219 }
220
221 pub async fn create_raw(&self, body: Value) -> Result<Value, ManagerError> {
224 let cfg = self.cfg.get().await?;
225 let cfg = cfg.as_ref();
226 with_retry(&self.retry, false, || {
228 post_json_body(cfg, "/api/v2/applications", &body)
229 })
230 .await
231 }
232
233 pub async fn get(&self, id: &str) -> Result<ApplicationItem, ManagerError> {
239 let cfg = self.cfg.get().await?;
240 let cfg = cfg.as_ref();
241 let path = format!(
242 "/api/v2/applications/{}",
243 crate::gen::manager::apis::urlencode(id)
244 );
245 with_retry(&self.retry, true, || async {
246 decode_item_envelope(get_json(cfg, &path).await?)
247 })
248 .await
249 }
250
251 pub async fn update(
256 &self,
257 id: &str,
258 body: models::ApplicationUpdateBody,
259 ) -> Result<ApplicationItem, ManagerError> {
260 let body = serde_json::to_value(&body)
261 .map_err(|e| ManagerError::InvalidArgument(e.to_string()))?;
262 decode_item_envelope(self.update_raw(id, body).await?)
263 }
264
265 pub async fn update_raw(&self, id: &str, body: Value) -> Result<Value, ManagerError> {
268 let cfg = self.cfg.get().await?;
269 let cfg = cfg.as_ref();
270 let path = format!(
271 "/api/v2/applications/{}",
272 crate::gen::manager::apis::urlencode(id)
273 );
274 with_retry(&self.retry, false, || put_json(cfg, &path, &body)).await
276 }
277
278 pub async fn delete(&self, id: &str) -> Result<(), ManagerError> {
280 let cfg = self.cfg.get().await?;
281 let cfg = cfg.as_ref();
282 with_retry(&self.retry, false, || api::delete_application(cfg, id))
283 .await
284 .map_err(map_manager_err)?;
285 Ok(())
286 }
287
288 pub async fn delete_many(
290 &self,
291 ids: Vec<String>,
292 ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
293 let cfg = self.cfg.get().await?;
294 let cfg = cfg.as_ref();
295 let body = models::DeleteManyApplicationsRequest { ids };
296 with_retry(&self.retry, false, || {
297 api::delete_many_applications(cfg, body.clone())
298 })
299 .await
300 .map_err(map_manager_err)
301 }
302
303 pub async fn list_modules(&self) -> Result<models::ListModulesResponse, ManagerError> {
305 let cfg = self.cfg.get().await?;
306 let cfg = cfg.as_ref();
307 with_retry(&self.retry, true, || api::list_modules(cfg))
308 .await
309 .map_err(map_manager_err)
310 }
311
312 pub async fn dispatch(
315 &self,
316 id: &str,
317 position: &str,
318 is_async: bool,
319 body: models::LocalAutomationDispatch,
320 ) -> Result<models::DispatchLocalAutomationsResponse, ManagerError> {
321 let cfg = self.cfg.get().await?;
322 let cfg = cfg.as_ref();
323 with_retry(&self.retry, false, || {
324 manager_api::dispatch_local_automations(cfg, id, position, is_async, Some(body.clone()))
325 })
326 .await
327 .map_err(map_manager_err)
328 }
329
330 pub async fn clone(&self, id: &str) -> Result<ApplicationItem, ManagerError> {
335 decode_item_envelope(self.clone_raw(id).await?)
336 }
337
338 pub async fn clone_raw(&self, id: &str) -> Result<Value, ManagerError> {
341 let cfg = self.cfg.get().await?;
342 let cfg = cfg.as_ref();
343 let path = format!(
344 "/api/v2/applications/{}/clone",
345 crate::gen::manager::apis::urlencode(id)
346 );
347 with_retry(&self.retry, false, || post_json(cfg, &path)).await
349 }
350
351 pub async fn bulk_update(
353 &self,
354 body: models::BulkUpdateApplicationsRequest,
355 ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
356 let cfg = self.cfg.get().await?;
357 let cfg = cfg.as_ref();
358 with_retry(&self.retry, false, || {
359 api::bulk_update_applications(cfg, body.clone())
360 })
361 .await
362 .map_err(map_manager_err)
363 }
364
365 pub async fn list_app_actions(&self) -> Result<models::ObjectListResponse, ManagerError> {
367 let cfg = self.cfg.get().await?;
368 let cfg = cfg.as_ref();
369 with_retry(&self.retry, true, || {
370 api::list_application_actions(cfg, None, None)
371 })
372 .await
373 .map_err(map_manager_err)
374 }
375
376 pub async fn list_errors(&self) -> Result<models::ObjectListResponse, ManagerError> {
378 let cfg = self.cfg.get().await?;
379 let cfg = cfg.as_ref();
380 with_retry(&self.retry, true, || api::list_application_errors(cfg))
381 .await
382 .map_err(map_manager_err)
383 }
384}
385
386pub struct LocalAutomationsResource {
388 pub(crate) cfg: SharedCfg<Configuration>,
389 pub(crate) retry: RetryPolicy,
390}
391
392impl LocalAutomationsResource {
393 pub async fn list_all(
395 &self,
396 application_id: &str,
397 ) -> Result<Vec<models::LocalAutomation>, ManagerError> {
398 let cfg = self.cfg.get().await?;
399 let cfg = cfg.as_ref();
400 collect_all(&self.retry, map_manager_err, |page| async move {
401 let r =
402 local_automation_api::list_local_automations(cfg, application_id, Some(page), None)
403 .await?;
404 Ok((r.items, r.pagination.pages, r.pagination.current))
405 })
406 .await
407 }
408
409 pub async fn create(
411 &self,
412 application_id: &str,
413 body: models::RestCreateLocalAutomation,
414 ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
415 let cfg = self.cfg.get().await?;
416 let cfg = cfg.as_ref();
417 with_retry(&self.retry, false, || {
418 local_automation_api::create_local_automation(cfg, application_id, body.clone())
419 })
420 .await
421 .map_err(map_manager_err)
422 }
423
424 pub async fn get(
426 &self,
427 application_id: &str,
428 id: &str,
429 ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
430 let cfg = self.cfg.get().await?;
431 let cfg = cfg.as_ref();
432 with_retry(&self.retry, true, || {
433 local_automation_api::get_local_automation(cfg, application_id, id)
434 })
435 .await
436 .map_err(map_manager_err)
437 }
438
439 pub async fn update(
441 &self,
442 application_id: &str,
443 id: &str,
444 body: models::RestUpdateLocalAutomation,
445 ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
446 let cfg = self.cfg.get().await?;
447 let cfg = cfg.as_ref();
448 with_retry(&self.retry, false, || {
449 local_automation_api::update_local_automation(cfg, application_id, id, body.clone())
450 })
451 .await
452 .map_err(map_manager_err)
453 }
454
455 pub async fn delete(&self, application_id: &str, id: &str) -> Result<(), ManagerError> {
457 let cfg = self.cfg.get().await?;
458 let cfg = cfg.as_ref();
459 with_retry(&self.retry, false, || {
460 local_automation_api::delete_local_automation(cfg, application_id, id)
461 })
462 .await
463 .map_err(map_manager_err)?;
464 Ok(())
465 }
466}