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((
116                    r.items,
117                    r.pagination.pages.unwrap_or(1),
118                    r.pagination.current.unwrap_or(1),
119                ))
120            },
121        )
122        .await
123    }
124
125    /// List all agents (unfiltered, auto-paginated) — [`AgentsResource::list`] with no filters.
126    pub async fn list_all(&self) -> Result<Vec<models::Agent>, ManagerError> {
127        self.list(ListAgentsQuery::default()).await
128    }
129
130    /// List one page of agents, optionally filtered server-side.
131    pub async fn list_page(
132        &self,
133        page: i32,
134        per_page: Option<i32>,
135        query: ListAgentsQuery,
136    ) -> Result<Page<models::Agent>, ManagerError> {
137        let cfg = self.cfg.get().await?;
138        let cfg = cfg.as_ref();
139        let query = &query;
140        fetch_page(
141            &self.retry,
142            |e| e,
143            || async move {
144                let v = get_json(cfg, &query.to_path(page, per_page)).await?;
145                let r: models::PaginatedAgentResponse =
146                    serde_json::from_value(v).map_err(|e| ManagerError::Decode(e.to_string()))?;
147                Ok((
148                    r.items,
149                    r.pagination.pages.unwrap_or(1),
150                    r.pagination.current.unwrap_or(1),
151                    r.pagination.total,
152                ))
153            },
154        )
155        .await
156    }
157
158    /// Create an agent.
159    pub async fn create(
160        &self,
161        body: models::RestCreateAgent,
162    ) -> Result<models::AgentItemResponse, ManagerError> {
163        let cfg = self.cfg.get().await?;
164        let cfg = cfg.as_ref();
165        with_retry(&self.retry, false, || {
166            agent_api::create_agent(cfg, body.clone())
167        })
168        .await
169        .map_err(map_manager_err)
170    }
171
172    /// Get an agent by id.
173    pub async fn get(&self, id: &str) -> Result<models::AgentItemResponse, ManagerError> {
174        let cfg = self.cfg.get().await?;
175        let cfg = cfg.as_ref();
176        with_retry(&self.retry, true, || agent_api::get_agent(cfg, id))
177            .await
178            .map_err(map_manager_err)
179    }
180
181    /// Update an agent.
182    pub async fn update(
183        &self,
184        id: &str,
185        body: models::RestUpdateAgent,
186    ) -> Result<models::AgentItemResponse, ManagerError> {
187        let cfg = self.cfg.get().await?;
188        let cfg = cfg.as_ref();
189        with_retry(&self.retry, false, || {
190            agent_api::update_agent(cfg, id, body.clone())
191        })
192        .await
193        .map_err(map_manager_err)
194    }
195
196    /// Delete an agent.
197    pub async fn delete(&self, id: &str) -> Result<(), ManagerError> {
198        let cfg = self.cfg.get().await?;
199        let cfg = cfg.as_ref();
200        with_retry(&self.retry, false, || agent_api::delete_agent(cfg, id))
201            .await
202            .map_err(map_manager_err)?;
203        Ok(())
204    }
205
206    /// Update an agent's status.
207    pub async fn update_status(
208        &self,
209        id: &str,
210        status: models::UpdateAgentStatusRequest,
211    ) -> Result<models::AgentStatus, ManagerError> {
212        let cfg = self.cfg.get().await?;
213        let cfg = cfg.as_ref();
214        with_retry(&self.retry, false, || {
215            manager_api::update_agent_status(cfg, id, Some(status.clone()))
216        })
217        .await
218        .map_err(map_manager_err)
219    }
220
221    /// List agent presences.
222    pub async fn presences(&self) -> Result<models::AgentPresenceListResponse, ManagerError> {
223        let cfg = self.cfg.get().await?;
224        let cfg = cfg.as_ref();
225        with_retry(&self.retry, true, || agent_api::list_agent_presences(cfg))
226            .await
227            .map_err(map_manager_err)
228    }
229
230    /// Get an agent presence by name.
231    pub async fn get_presence(
232        &self,
233        name: &str,
234    ) -> Result<models::AgentPresenceItemResponse, ManagerError> {
235        let cfg = self.cfg.get().await?;
236        let cfg = cfg.as_ref();
237        with_retry(&self.retry, true, || {
238            agent_api::get_agent_presence(cfg, name)
239        })
240        .await
241        .map_err(map_manager_err)
242    }
243
244    /// Create an agent presence.
245    pub async fn create_presence(
246        &self,
247        body: models::AgentPresenceWriteBody,
248    ) -> Result<models::AgentPresenceItemResponse, ManagerError> {
249        let cfg = self.cfg.get().await?;
250        let cfg = cfg.as_ref();
251        with_retry(&self.retry, false, || {
252            agent_api::create_agent_presence(cfg, body.clone())
253        })
254        .await
255        .map_err(map_manager_err)
256    }
257
258    /// Update an agent presence.
259    pub async fn update_presence(
260        &self,
261        name: &str,
262        body: models::AgentPresenceWriteBody,
263    ) -> Result<models::AgentPresenceItemResponse, ManagerError> {
264        let cfg = self.cfg.get().await?;
265        let cfg = cfg.as_ref();
266        with_retry(&self.retry, false, || {
267            agent_api::update_agent_presence(cfg, name, body.clone())
268        })
269        .await
270        .map_err(map_manager_err)
271    }
272
273    /// Delete an agent presence.
274    pub async fn delete_presence(
275        &self,
276        name: &str,
277    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
278        let cfg = self.cfg.get().await?;
279        let cfg = cfg.as_ref();
280        with_retry(&self.retry, false, || {
281            agent_api::delete_agent_presence(cfg, name)
282        })
283        .await
284        .map_err(map_manager_err)
285    }
286
287    /// Get an agent's total status by id.
288    pub async fn get_status(
289        &self,
290        id: &str,
291    ) -> Result<models::AgentTotalStatusResponse, ManagerError> {
292        let cfg = self.cfg.get().await?;
293        let cfg = cfg.as_ref();
294        with_retry(&self.retry, true, || agent_api::get_agent_status(cfg, id))
295            .await
296            .map_err(map_manager_err)
297    }
298
299    /// List available agent statuses.
300    pub async fn available_statuses(
301        &self,
302    ) -> Result<models::AgentAvailabilityListResponse, ManagerError> {
303        let cfg = self.cfg.get().await?;
304        let cfg = cfg.as_ref();
305        with_retry(&self.retry, true, || {
306            agent_api::list_available_agent_statuses(cfg)
307        })
308        .await
309        .map_err(map_manager_err)
310    }
311
312    /// Enable an agent.
313    pub async fn enable(&self, id: &str) -> Result<models::DefaultV2MessageResponse, ManagerError> {
314        let cfg = self.cfg.get().await?;
315        let cfg = cfg.as_ref();
316        with_retry(&self.retry, false, || agent_api::enable_agent(cfg, id))
317            .await
318            .map_err(map_manager_err)
319    }
320
321    /// Disable an agent.
322    pub async fn disable(
323        &self,
324        id: &str,
325    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
326        let cfg = self.cfg.get().await?;
327        let cfg = cfg.as_ref();
328        with_retry(&self.retry, false, || agent_api::disable_agent(cfg, id))
329            .await
330            .map_err(map_manager_err)
331    }
332
333    /// Hang up an agent's active call.
334    pub async fn hangup_call(
335        &self,
336        id: &str,
337    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
338        let cfg = self.cfg.get().await?;
339        let cfg = cfg.as_ref();
340        with_retry(&self.retry, false, || agent_api::hangup_agent_call(cfg, id))
341            .await
342            .map_err(map_manager_err)
343    }
344
345    /// Perform a bulk action (e.g. `enable`, `disable`, `delete`) on multiple agents.
346    pub async fn bulk_action(
347        &self,
348        action: &str,
349        body: models::AgentBulkRequest,
350    ) -> Result<models::AgentBulkResponse, ManagerError> {
351        let cfg = self.cfg.get().await?;
352        let cfg = cfg.as_ref();
353        with_retry(&self.retry, false, || {
354            agent_api::bulk_agent_action(cfg, action, body.clone())
355        })
356        .await
357        .map_err(map_manager_err)
358    }
359
360    /// Export all agents in the given format.
361    pub async fn export(&self, format: &str) -> Result<String, ManagerError> {
362        let cfg = self.cfg.get().await?;
363        let cfg = cfg.as_ref();
364        with_retry(&self.retry, true, || agent_api::export_agents(cfg, format))
365            .await
366            .map_err(map_manager_err)
367    }
368
369    /// Import agents from a file in the given format.
370    #[allow(clippy::too_many_arguments)]
371    pub async fn import_agents(
372        &self,
373        format: &str,
374        file: std::path::PathBuf,
375        r#async: Option<bool>,
376        create_only: Option<bool>,
377        update_only: Option<bool>,
378        delete_unlisted: Option<bool>,
379    ) -> Result<models::AgentImportValidationResults, ManagerError> {
380        let cfg = self.cfg.get().await?;
381        let cfg = cfg.as_ref();
382        with_retry(&self.retry, false, || {
383            agent_api::import_agents(
384                cfg,
385                format,
386                file.clone(),
387                r#async,
388                create_only,
389                update_only,
390                delete_unlisted,
391            )
392        })
393        .await
394        .map_err(map_manager_err)
395    }
396
397    /// Validate an agent import file without applying it.
398    pub async fn validate_import(
399        &self,
400        file: std::path::PathBuf,
401    ) -> Result<models::AgentImportValidationResults, ManagerError> {
402        let cfg = self.cfg.get().await?;
403        let cfg = cfg.as_ref();
404        with_retry(&self.retry, false, || {
405            agent_api::validate_agent_import(cfg, file.clone())
406        })
407        .await
408        .map_err(map_manager_err)
409    }
410
411    /// Get an agent import job by id.
412    pub async fn get_import_job(
413        &self,
414        id: &str,
415    ) -> Result<models::AgentImportJobItemResponse, ManagerError> {
416        let cfg = self.cfg.get().await?;
417        let cfg = cfg.as_ref();
418        with_retry(&self.retry, true, || {
419            agent_api::get_agent_import_job(cfg, id)
420        })
421        .await
422        .map_err(map_manager_err)
423    }
424
425    /// List an agent's logs (auto-paginated), optionally filtered by a `from`/`to` range.
426    pub async fn logs(
427        &self,
428        id: &str,
429        from: Option<i32>,
430        to: Option<i32>,
431    ) -> Result<Vec<models::AgentLogEntry>, ManagerError> {
432        let cfg = self.cfg.get().await?;
433        let cfg = cfg.as_ref();
434        collect_all(&self.retry, map_manager_err, |page| async move {
435            let r = agent_api::list_agent_logs(cfg, id, Some(page), None, from, to).await?;
436            let (pages, current) = r
437                .pagination
438                .as_ref()
439                .map(|p| (p.pages.unwrap_or(1), p.current.unwrap_or(1)))
440                .unwrap_or((1, 1));
441            Ok((r.items, pages, current))
442        })
443        .await
444    }
445
446    /// List logs across all agents (auto-paginated).
447    pub async fn all_logs(&self) -> Result<Vec<models::AgentLogEntry>, ManagerError> {
448        let cfg = self.cfg.get().await?;
449        let cfg = cfg.as_ref();
450        collect_all(&self.retry, map_manager_err, |page| async move {
451            let r = agent_api::list_all_agent_logs(cfg, Some(page), None).await?;
452            let (pages, current) = r
453                .pagination
454                .as_ref()
455                .map(|p| (p.pages.unwrap_or(1), p.current.unwrap_or(1)))
456                .unwrap_or((1, 1));
457            Ok((r.items, pages, current))
458        })
459        .await
460    }
461
462    /// Push a message to an agent (or broadcast to all agents).
463    pub async fn push(
464        &self,
465        body: models::AgentPushRequest,
466    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
467        let cfg = self.cfg.get().await?;
468        let cfg = cfg.as_ref();
469        with_retry(&self.retry, false, || {
470            agent_api::push_to_agent(cfg, body.clone())
471        })
472        .await
473        .map_err(map_manager_err)
474    }
475
476    /// Update an agent's password.
477    pub async fn update_password(
478        &self,
479        id: &str,
480        body: models::AgentPasswordUpdateRequest,
481    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
482        let cfg = self.cfg.get().await?;
483        let cfg = cfg.as_ref();
484        with_retry(&self.retry, false, || {
485            agent_api::update_agent_password(cfg, id, body.clone())
486        })
487        .await
488        .map_err(map_manager_err)
489    }
490}
491
492/// Agent groups — `/api/v2/agents/groups`.
493pub struct AgentGroupsResource {
494    pub(crate) cfg: SharedCfg<Configuration>,
495    pub(crate) retry: RetryPolicy,
496}
497
498impl AgentGroupsResource {
499    /// List all agent groups (auto-paginated).
500    pub async fn list_all(&self) -> Result<Vec<models::AgentGroup>, ManagerError> {
501        let cfg = self.cfg.get().await?;
502        let cfg = cfg.as_ref();
503        collect_all(&self.retry, map_manager_err, |page| async move {
504            let r = agent_group_api::list_agent_groups(cfg, Some(page), None).await?;
505            Ok((
506                r.items,
507                r.pagination.pages.unwrap_or(1),
508                r.pagination.current.unwrap_or(1),
509            ))
510        })
511        .await
512    }
513
514    /// Create an agent group.
515    pub async fn create(
516        &self,
517        body: models::RestCreateAgentGroup,
518    ) -> Result<models::AgentGroupItemResponse, ManagerError> {
519        let cfg = self.cfg.get().await?;
520        let cfg = cfg.as_ref();
521        with_retry(&self.retry, false, || {
522            agent_group_api::create_agent_group(cfg, body.clone())
523        })
524        .await
525        .map_err(map_manager_err)
526    }
527
528    /// Get an agent group by id.
529    pub async fn get(&self, id: &str) -> Result<models::AgentGroupItemResponse, ManagerError> {
530        let cfg = self.cfg.get().await?;
531        let cfg = cfg.as_ref();
532        with_retry(&self.retry, true, || {
533            agent_group_api::get_agent_group(cfg, id)
534        })
535        .await
536        .map_err(map_manager_err)
537    }
538
539    /// Update an agent group.
540    pub async fn update(
541        &self,
542        id: &str,
543        body: models::RestUpdateAgentGroup,
544    ) -> Result<models::AgentGroupItemResponse, ManagerError> {
545        let cfg = self.cfg.get().await?;
546        let cfg = cfg.as_ref();
547        with_retry(&self.retry, false, || {
548            agent_group_api::update_agent_group(cfg, id, body.clone())
549        })
550        .await
551        .map_err(map_manager_err)
552    }
553
554    /// Delete an agent group.
555    pub async fn delete(&self, id: &str) -> Result<(), ManagerError> {
556        let cfg = self.cfg.get().await?;
557        let cfg = cfg.as_ref();
558        with_retry(&self.retry, false, || {
559            agent_group_api::delete_agent_group(cfg, id)
560        })
561        .await
562        .map_err(map_manager_err)?;
563        Ok(())
564    }
565
566    /// Add an agent to a group.
567    pub async fn add_agent(
568        &self,
569        group_id: &str,
570        agent_id: &str,
571    ) -> Result<models::AgentGroupAdditionResponse, ManagerError> {
572        let cfg = self.cfg.get().await?;
573        let cfg = cfg.as_ref();
574        let id = uuid::Uuid::parse_str(agent_id)
575            .map_err(|e| ManagerError::InvalidArgument(format!("agent_id: {e}")))?;
576        let body = models::AddAgentToGroupRequest { id };
577        with_retry(&self.retry, false, || {
578            manager_api::add_agent_to_group(cfg, group_id, Some(body.clone()))
579        })
580        .await
581        .map_err(map_manager_err)
582    }
583
584    /// Remove an agent from a group.
585    pub async fn remove_agent(
586        &self,
587        group_id: &str,
588        agent_id: &str,
589    ) -> Result<models::AgentGroupItemResponse, ManagerError> {
590        let cfg = self.cfg.get().await?;
591        let cfg = cfg.as_ref();
592        with_retry(&self.retry, false, || {
593            agent_group_api::remove_agent_from_group(cfg, group_id, agent_id)
594        })
595        .await
596        .map_err(map_manager_err)
597    }
598
599    /// List all agents in a group (auto-paginated).
600    pub async fn list_agents(&self, group_id: &str) -> Result<Vec<models::Agent>, ManagerError> {
601        let cfg = self.cfg.get().await?;
602        let cfg = cfg.as_ref();
603        collect_all(&self.retry, map_manager_err, |page| async move {
604            let r = agent_group_api::list_agents_in_group(cfg, group_id, Some(page), None).await?;
605            Ok((
606                r.items,
607                r.pagination.pages.unwrap_or(1),
608                r.pagination.current.unwrap_or(1),
609            ))
610        })
611        .await
612    }
613
614    /// Bulk-delete agent groups by id.
615    pub async fn bulk_delete(
616        &self,
617        ids: Vec<String>,
618    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
619        let cfg = self.cfg.get().await?;
620        let cfg = cfg.as_ref();
621        let ids = ids
622            .iter()
623            .map(|id| {
624                uuid::Uuid::parse_str(id)
625                    .map_err(|e| ManagerError::InvalidArgument(format!("ids: {e}")))
626            })
627            .collect::<Result<Vec<_>, _>>()?;
628        let body = models::BulkIdsRequest { ids };
629        with_retry(&self.retry, false, || {
630            agent_group_api::bulk_delete_agent_groups(cfg, body.clone())
631        })
632        .await
633        .map_err(map_manager_err)
634    }
635}