Skip to main content

babelforce_manager_sdk/resources/
agents.rs

1use crate::error::{map_manager_err, ManagerError};
2use crate::gen::manager::apis::configuration::Configuration;
3use crate::gen::manager::apis::{agent_api, agent_group_api, manager_api};
4use crate::gen::manager::models;
5use crate::http::{collect_all, fetch_page, Page};
6use crate::resources::raw::get_json;
7use crate::retry::{with_retry, RetryPolicy};
8use crate::token::SharedCfg;
9
10/// Optional server-side filters for the agents list surfaces ([`AgentsResource::list`] /
11/// [`AgentsResource::list_page`]). All fields combine; the default filters nothing.
12#[derive(Debug, Clone, Default)]
13pub struct ListAgentsQuery {
14    /// Free-text search across name, group name, number, email, sourceId and integration label.
15    pub q: Option<String>,
16    /// Only enabled (`true`) or only disabled (`false`) agents.
17    pub enabled: Option<bool>,
18    /// Filter by agent name.
19    pub name: Option<String>,
20    /// Filter by the agent's number.
21    pub number: Option<String>,
22    /// Filter by integration source id.
23    pub source_id: Option<String>,
24    /// Filter by line status.
25    pub state: Option<models::AgentLineStatus>,
26    /// Filter by source integration.
27    pub source: Option<String>,
28    /// Restrict to agents in these group id(s). A hyphenated UUID is normalized to the API's
29    /// unhyphenated form, so a listed group id round-trips as a filter.
30    pub group_ids: Vec<String>,
31    /// Restrict to agents in these group name(s).
32    pub groups: Vec<String>,
33    /// Filter by tag(s).
34    pub tags: Vec<String>,
35}
36
37impl ListAgentsQuery {
38    /// Serialize as `/api/v2/agents?...` with the spec's form/explode semantics — array filters
39    /// travel as repeated `key=value` pairs.
40    fn to_path(&self, page: i32, max: Option<i32>) -> String {
41        let mut ser = url::form_urlencoded::Serializer::new(String::new());
42        ser.append_pair("page", &page.to_string());
43        if let Some(max) = max {
44            ser.append_pair("max", &max.to_string());
45        }
46        if let Some(v) = &self.q {
47            ser.append_pair("q", v);
48        }
49        if let Some(v) = self.enabled {
50            ser.append_pair("enabled", if v { "true" } else { "false" });
51        }
52        if let Some(v) = &self.name {
53            ser.append_pair("name", v);
54        }
55        if let Some(v) = &self.number {
56            ser.append_pair("number", v);
57        }
58        if let Some(v) = &self.source_id {
59            ser.append_pair("sourceId", v);
60        }
61        if let Some(v) = &self.state {
62            ser.append_pair("state", &v.to_string());
63        }
64        if let Some(v) = &self.source {
65            ser.append_pair("source", v);
66        }
67        for id in &self.group_ids {
68            ser.append_pair("groupIds", &normalize_entity_id(id));
69        }
70        for g in &self.groups {
71            ser.append_pair("groups", g);
72        }
73        for t in &self.tags {
74            ser.append_pair("tags", t);
75        }
76        format!("/api/v2/agents?{}", ser.finish())
77    }
78}
79
80/// babelforce entity ids are unhyphenated 32-char hex; normalize a hyphenated UUID the same way
81/// the generated path helper does. Non-UUID values pass through unchanged.
82fn normalize_entity_id(v: &str) -> String {
83    match uuid::Uuid::parse_str(v) {
84        Ok(u) => u.simple().to_string(),
85        Err(_) => v.to_string(),
86    }
87}
88
89/// Agent management — `/api/v2/agents`, with a nested `groups` sub-resource.
90pub struct AgentsResource {
91    pub(crate) cfg: SharedCfg<Configuration>,
92    pub(crate) retry: RetryPolicy,
93    /// Agent groups — `/api/v2/agents/groups`.
94    pub groups: AgentGroupsResource,
95}
96
97impl AgentsResource {
98    /// List agents matching `query`, auto-paginated across pages.
99    ///
100    /// Stands in for the generated `list_agents`, whose `groupIds`/`groups`/`tags` parameters
101    /// (anyOf string-or-array types) are JSON-serialized into the query — a single id would be
102    /// sent as `?groupIds="…"` with literal quotes, which the server does not match. The facade
103    /// builds the form/explode query itself and decodes into the same generated response model.
104    pub async fn list(&self, query: ListAgentsQuery) -> Result<Vec<models::Agent>, ManagerError> {
105        let cfg = self.cfg.get().await?;
106        let cfg = cfg.as_ref();
107        let query = &query;
108        collect_all(
109            &self.retry,
110            |e| e,
111            |page| async move {
112                let v = get_json(cfg, &query.to_path(page, None)).await?;
113                let r: models::PaginatedAgentResponse =
114                    serde_json::from_value(v).map_err(|e| ManagerError::Decode(e.to_string()))?;
115                Ok((r.items, r.pagination.pages, r.pagination.current))
116            },
117        )
118        .await
119    }
120
121    /// List all agents (unfiltered, auto-paginated) — [`AgentsResource::list`] with no filters.
122    pub async fn list_all(&self) -> Result<Vec<models::Agent>, ManagerError> {
123        self.list(ListAgentsQuery::default()).await
124    }
125
126    /// List one page of agents, optionally filtered server-side.
127    pub async fn list_page(
128        &self,
129        page: i32,
130        per_page: Option<i32>,
131        query: ListAgentsQuery,
132    ) -> Result<Page<models::Agent>, ManagerError> {
133        let cfg = self.cfg.get().await?;
134        let cfg = cfg.as_ref();
135        let query = &query;
136        fetch_page(
137            &self.retry,
138            |e| e,
139            || async move {
140                let v = get_json(cfg, &query.to_path(page, per_page)).await?;
141                let r: models::PaginatedAgentResponse =
142                    serde_json::from_value(v).map_err(|e| ManagerError::Decode(e.to_string()))?;
143                Ok((
144                    r.items,
145                    r.pagination.pages,
146                    r.pagination.current,
147                    Some(r.pagination.total),
148                ))
149            },
150        )
151        .await
152    }
153
154    /// Create an agent.
155    pub async fn create(
156        &self,
157        body: models::RestCreateAgent,
158    ) -> Result<models::AgentItemResponse, ManagerError> {
159        let cfg = self.cfg.get().await?;
160        let cfg = cfg.as_ref();
161        with_retry(&self.retry, false, || {
162            agent_api::create_agent(cfg, body.clone())
163        })
164        .await
165        .map_err(map_manager_err)
166    }
167
168    /// Get an agent by id.
169    pub async fn get(&self, id: &str) -> Result<models::AgentItemResponse, ManagerError> {
170        let cfg = self.cfg.get().await?;
171        let cfg = cfg.as_ref();
172        with_retry(&self.retry, true, || agent_api::get_agent(cfg, id))
173            .await
174            .map_err(map_manager_err)
175    }
176
177    /// Update an agent.
178    pub async fn update(
179        &self,
180        id: &str,
181        body: models::RestUpdateAgent,
182    ) -> Result<models::AgentItemResponse, ManagerError> {
183        let cfg = self.cfg.get().await?;
184        let cfg = cfg.as_ref();
185        with_retry(&self.retry, false, || {
186            agent_api::update_agent(cfg, id, body.clone())
187        })
188        .await
189        .map_err(map_manager_err)
190    }
191
192    /// Delete an agent.
193    pub async fn delete(&self, id: &str) -> Result<(), ManagerError> {
194        let cfg = self.cfg.get().await?;
195        let cfg = cfg.as_ref();
196        with_retry(&self.retry, false, || agent_api::delete_agent(cfg, id))
197            .await
198            .map_err(map_manager_err)?;
199        Ok(())
200    }
201
202    /// Update an agent's status.
203    pub async fn update_status(
204        &self,
205        id: &str,
206        status: models::UpdateAgentStatusRequest,
207    ) -> Result<models::AgentStatus, ManagerError> {
208        let cfg = self.cfg.get().await?;
209        let cfg = cfg.as_ref();
210        with_retry(&self.retry, false, || {
211            manager_api::update_agent_status(cfg, id, Some(status.clone()))
212        })
213        .await
214        .map_err(map_manager_err)
215    }
216
217    /// List agent presences.
218    pub async fn presences(&self) -> Result<models::AgentPresenceListResponse, ManagerError> {
219        let cfg = self.cfg.get().await?;
220        let cfg = cfg.as_ref();
221        with_retry(&self.retry, true, || agent_api::list_agent_presences(cfg))
222            .await
223            .map_err(map_manager_err)
224    }
225
226    /// Get an agent presence by name.
227    pub async fn get_presence(
228        &self,
229        name: &str,
230    ) -> Result<models::AgentPresenceItemResponse, ManagerError> {
231        let cfg = self.cfg.get().await?;
232        let cfg = cfg.as_ref();
233        with_retry(&self.retry, true, || {
234            agent_api::get_agent_presence(cfg, name)
235        })
236        .await
237        .map_err(map_manager_err)
238    }
239
240    /// Create an agent presence.
241    pub async fn create_presence(
242        &self,
243        body: models::AgentPresenceWriteBody,
244    ) -> Result<models::AgentPresenceItemResponse, ManagerError> {
245        let cfg = self.cfg.get().await?;
246        let cfg = cfg.as_ref();
247        with_retry(&self.retry, false, || {
248            agent_api::create_agent_presence(cfg, body.clone())
249        })
250        .await
251        .map_err(map_manager_err)
252    }
253
254    /// Update an agent presence.
255    pub async fn update_presence(
256        &self,
257        name: &str,
258        body: models::AgentPresenceWriteBody,
259    ) -> Result<models::AgentPresenceItemResponse, ManagerError> {
260        let cfg = self.cfg.get().await?;
261        let cfg = cfg.as_ref();
262        with_retry(&self.retry, false, || {
263            agent_api::update_agent_presence(cfg, name, body.clone())
264        })
265        .await
266        .map_err(map_manager_err)
267    }
268
269    /// Delete an agent presence.
270    pub async fn delete_presence(
271        &self,
272        name: &str,
273    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
274        let cfg = self.cfg.get().await?;
275        let cfg = cfg.as_ref();
276        with_retry(&self.retry, false, || {
277            agent_api::delete_agent_presence(cfg, name)
278        })
279        .await
280        .map_err(map_manager_err)
281    }
282
283    /// Get an agent's total status by id.
284    pub async fn get_status(
285        &self,
286        id: &str,
287    ) -> Result<models::AgentTotalStatusResponse, ManagerError> {
288        let cfg = self.cfg.get().await?;
289        let cfg = cfg.as_ref();
290        with_retry(&self.retry, true, || agent_api::get_agent_status(cfg, id))
291            .await
292            .map_err(map_manager_err)
293    }
294
295    /// List available agent statuses.
296    pub async fn available_statuses(
297        &self,
298    ) -> Result<models::AgentAvailabilityListResponse, ManagerError> {
299        let cfg = self.cfg.get().await?;
300        let cfg = cfg.as_ref();
301        with_retry(&self.retry, true, || {
302            agent_api::list_available_agent_statuses(cfg)
303        })
304        .await
305        .map_err(map_manager_err)
306    }
307
308    /// Enable an agent.
309    pub async fn enable(&self, id: &str) -> Result<models::DefaultV2MessageResponse, ManagerError> {
310        let cfg = self.cfg.get().await?;
311        let cfg = cfg.as_ref();
312        with_retry(&self.retry, false, || agent_api::enable_agent(cfg, id))
313            .await
314            .map_err(map_manager_err)
315    }
316
317    /// Disable an agent.
318    pub async fn disable(
319        &self,
320        id: &str,
321    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
322        let cfg = self.cfg.get().await?;
323        let cfg = cfg.as_ref();
324        with_retry(&self.retry, false, || agent_api::disable_agent(cfg, id))
325            .await
326            .map_err(map_manager_err)
327    }
328
329    /// Hang up an agent's active call.
330    pub async fn hangup_call(
331        &self,
332        id: &str,
333    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
334        let cfg = self.cfg.get().await?;
335        let cfg = cfg.as_ref();
336        with_retry(&self.retry, false, || agent_api::hangup_agent_call(cfg, id))
337            .await
338            .map_err(map_manager_err)
339    }
340
341    /// Perform a bulk action (e.g. `enable`, `disable`, `delete`) on multiple agents.
342    pub async fn bulk_action(
343        &self,
344        action: &str,
345        body: models::AgentBulkRequest,
346    ) -> Result<models::AgentBulkResponse, ManagerError> {
347        let cfg = self.cfg.get().await?;
348        let cfg = cfg.as_ref();
349        with_retry(&self.retry, false, || {
350            agent_api::bulk_agent_action(cfg, action, body.clone())
351        })
352        .await
353        .map_err(map_manager_err)
354    }
355
356    /// Export all agents in the given format.
357    pub async fn export(&self, format: &str) -> Result<String, ManagerError> {
358        let cfg = self.cfg.get().await?;
359        let cfg = cfg.as_ref();
360        with_retry(&self.retry, true, || agent_api::export_agents(cfg, format))
361            .await
362            .map_err(map_manager_err)
363    }
364
365    /// Import agents from a file in the given format.
366    #[allow(clippy::too_many_arguments)]
367    pub async fn import_agents(
368        &self,
369        format: &str,
370        file: std::path::PathBuf,
371        r#async: Option<bool>,
372        create_only: Option<bool>,
373        update_only: Option<bool>,
374        delete_unlisted: Option<bool>,
375    ) -> Result<models::AgentImportValidationResults, ManagerError> {
376        let cfg = self.cfg.get().await?;
377        let cfg = cfg.as_ref();
378        with_retry(&self.retry, false, || {
379            agent_api::import_agents(
380                cfg,
381                format,
382                file.clone(),
383                r#async,
384                create_only,
385                update_only,
386                delete_unlisted,
387            )
388        })
389        .await
390        .map_err(map_manager_err)
391    }
392
393    /// Validate an agent import file without applying it.
394    pub async fn validate_import(
395        &self,
396        file: std::path::PathBuf,
397    ) -> Result<models::AgentImportValidationResults, ManagerError> {
398        let cfg = self.cfg.get().await?;
399        let cfg = cfg.as_ref();
400        with_retry(&self.retry, false, || {
401            agent_api::validate_agent_import(cfg, file.clone())
402        })
403        .await
404        .map_err(map_manager_err)
405    }
406
407    /// Get an agent import job by id.
408    pub async fn get_import_job(
409        &self,
410        id: &str,
411    ) -> Result<models::AgentImportJobItemResponse, ManagerError> {
412        let cfg = self.cfg.get().await?;
413        let cfg = cfg.as_ref();
414        with_retry(&self.retry, true, || {
415            agent_api::get_agent_import_job(cfg, id)
416        })
417        .await
418        .map_err(map_manager_err)
419    }
420
421    /// List an agent's logs (auto-paginated), optionally filtered by a `from`/`to` range.
422    pub async fn logs(
423        &self,
424        id: &str,
425        from: Option<i32>,
426        to: Option<i32>,
427    ) -> Result<Vec<models::AgentLogEntry>, ManagerError> {
428        let cfg = self.cfg.get().await?;
429        let cfg = cfg.as_ref();
430        collect_all(&self.retry, map_manager_err, |page| async move {
431            let r = agent_api::list_agent_logs(cfg, id, Some(page), None, from, to).await?;
432            let (pages, current) = r
433                .pagination
434                .as_ref()
435                .map(|p| (p.pages, p.current))
436                .unwrap_or((1, 1));
437            Ok((r.items, pages, current))
438        })
439        .await
440    }
441
442    /// List logs across all agents (auto-paginated).
443    pub async fn all_logs(&self) -> Result<Vec<models::AgentLogEntry>, ManagerError> {
444        let cfg = self.cfg.get().await?;
445        let cfg = cfg.as_ref();
446        collect_all(&self.retry, map_manager_err, |page| async move {
447            let r = agent_api::list_all_agent_logs(cfg, Some(page), None).await?;
448            let (pages, current) = r
449                .pagination
450                .as_ref()
451                .map(|p| (p.pages, p.current))
452                .unwrap_or((1, 1));
453            Ok((r.items, pages, current))
454        })
455        .await
456    }
457
458    /// Push a message to an agent (or broadcast to all agents).
459    pub async fn push(
460        &self,
461        body: models::AgentPushRequest,
462    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
463        let cfg = self.cfg.get().await?;
464        let cfg = cfg.as_ref();
465        with_retry(&self.retry, false, || {
466            agent_api::push_to_agent(cfg, body.clone())
467        })
468        .await
469        .map_err(map_manager_err)
470    }
471
472    /// Update an agent's password.
473    pub async fn update_password(
474        &self,
475        id: &str,
476        body: models::AgentPasswordUpdateRequest,
477    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
478        let cfg = self.cfg.get().await?;
479        let cfg = cfg.as_ref();
480        with_retry(&self.retry, false, || {
481            agent_api::update_agent_password(cfg, id, body.clone())
482        })
483        .await
484        .map_err(map_manager_err)
485    }
486}
487
488/// Agent groups — `/api/v2/agents/groups`.
489pub struct AgentGroupsResource {
490    pub(crate) cfg: SharedCfg<Configuration>,
491    pub(crate) retry: RetryPolicy,
492}
493
494impl AgentGroupsResource {
495    /// List all agent groups (auto-paginated).
496    pub async fn list_all(&self) -> Result<Vec<models::AgentGroup>, ManagerError> {
497        let cfg = self.cfg.get().await?;
498        let cfg = cfg.as_ref();
499        collect_all(&self.retry, map_manager_err, |page| async move {
500            let r = agent_group_api::list_agent_groups(cfg, Some(page), None).await?;
501            Ok((r.items, r.pagination.pages, r.pagination.current))
502        })
503        .await
504    }
505
506    /// Create an agent group.
507    pub async fn create(
508        &self,
509        body: models::RestCreateAgentGroup,
510    ) -> Result<models::AgentGroupItemResponse, ManagerError> {
511        let cfg = self.cfg.get().await?;
512        let cfg = cfg.as_ref();
513        with_retry(&self.retry, false, || {
514            agent_group_api::create_agent_group(cfg, body.clone())
515        })
516        .await
517        .map_err(map_manager_err)
518    }
519
520    /// Get an agent group by id.
521    pub async fn get(&self, id: &str) -> Result<models::AgentGroupItemResponse, ManagerError> {
522        let cfg = self.cfg.get().await?;
523        let cfg = cfg.as_ref();
524        with_retry(&self.retry, true, || {
525            agent_group_api::get_agent_group(cfg, id)
526        })
527        .await
528        .map_err(map_manager_err)
529    }
530
531    /// Update an agent group.
532    pub async fn update(
533        &self,
534        id: &str,
535        body: models::RestUpdateAgentGroup,
536    ) -> Result<models::AgentGroupItemResponse, ManagerError> {
537        let cfg = self.cfg.get().await?;
538        let cfg = cfg.as_ref();
539        with_retry(&self.retry, false, || {
540            agent_group_api::update_agent_group(cfg, id, body.clone())
541        })
542        .await
543        .map_err(map_manager_err)
544    }
545
546    /// Delete an agent group.
547    pub async fn delete(&self, id: &str) -> Result<(), ManagerError> {
548        let cfg = self.cfg.get().await?;
549        let cfg = cfg.as_ref();
550        with_retry(&self.retry, false, || {
551            agent_group_api::delete_agent_group(cfg, id)
552        })
553        .await
554        .map_err(map_manager_err)?;
555        Ok(())
556    }
557
558    /// Add an agent to a group.
559    pub async fn add_agent(
560        &self,
561        group_id: &str,
562        agent_id: &str,
563    ) -> Result<models::AgentGroupAdditionResponse, ManagerError> {
564        let cfg = self.cfg.get().await?;
565        let cfg = cfg.as_ref();
566        let id = uuid::Uuid::parse_str(agent_id)
567            .map_err(|e| ManagerError::InvalidArgument(format!("agent_id: {e}")))?;
568        let body = models::AddAgentToGroupRequest { id };
569        with_retry(&self.retry, false, || {
570            manager_api::add_agent_to_group(cfg, group_id, Some(body.clone()))
571        })
572        .await
573        .map_err(map_manager_err)
574    }
575
576    /// Remove an agent from a group.
577    pub async fn remove_agent(
578        &self,
579        group_id: &str,
580        agent_id: &str,
581    ) -> Result<models::AgentGroupItemResponse, ManagerError> {
582        let cfg = self.cfg.get().await?;
583        let cfg = cfg.as_ref();
584        with_retry(&self.retry, false, || {
585            agent_group_api::remove_agent_from_group(cfg, group_id, agent_id)
586        })
587        .await
588        .map_err(map_manager_err)
589    }
590
591    /// List all agents in a group (auto-paginated).
592    pub async fn list_agents(&self, group_id: &str) -> Result<Vec<models::Agent>, ManagerError> {
593        let cfg = self.cfg.get().await?;
594        let cfg = cfg.as_ref();
595        collect_all(&self.retry, map_manager_err, |page| async move {
596            let r = agent_group_api::list_agents_in_group(cfg, group_id, Some(page), None).await?;
597            Ok((r.items, r.pagination.pages, r.pagination.current))
598        })
599        .await
600    }
601
602    /// Bulk-delete agent groups by id.
603    pub async fn bulk_delete(
604        &self,
605        ids: Vec<String>,
606    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
607        let cfg = self.cfg.get().await?;
608        let cfg = cfg.as_ref();
609        let ids = ids
610            .iter()
611            .map(|id| {
612                uuid::Uuid::parse_str(id)
613                    .map_err(|e| ManagerError::InvalidArgument(format!("ids: {e}")))
614            })
615            .collect::<Result<Vec<_>, _>>()?;
616        let body = models::BulkIdsRequest { ids };
617        with_retry(&self.retry, false, || {
618            agent_group_api::bulk_delete_agent_groups(cfg, body.clone())
619        })
620        .await
621        .map_err(map_manager_err)
622    }
623}