babelforce-manager-sdk 0.42.3

Rust SDK for the babelforce manager APIs — auth, user & agent management, call reporting, metrics, and task automations.
Documentation
use std::sync::Arc;

use serde_json::Value;

use crate::error::{map_manager_err, ManagerError};
use crate::gen::manager::apis::configuration::Configuration;
use crate::gen::manager::apis::{application_api as api, local_automation_api, manager_api};
use crate::gen::manager::models;
use crate::http::{collect_all, fetch_page, Page};
use crate::resources::raw::{get_json, post_json, post_json_body};
use crate::retry::{with_retry, RetryPolicy};

/// Decode one application object **variant-directly**.
///
/// The generated [`models::Application`] is an internally-tagged enum (`#[serde(tag = "module")]`),
/// so serde strips the tag before deserializing the variant content — but every generated variant
/// struct *requires* its own `module` field, making the derived `Deserialize` fail on **every**
/// real payload (an openapi-generator discriminator artifact). The facade reads the tag itself and
/// deserializes the full (tag-carrying) object straight into the variant struct — no generated
/// code touched.
fn decode_application(v: Value) -> Result<models::Application, ManagerError> {
    use models::Application as App;
    fn var<T: serde::de::DeserializeOwned>(v: Value) -> Result<Box<T>, ManagerError> {
        serde_json::from_value(v)
            .map(Box::new)
            .map_err(|e| ManagerError::Decode(e.to_string()))
    }
    let module = v
        .get("module")
        .and_then(Value::as_str)
        .unwrap_or_default()
        .to_string();
    Ok(match module.as_str() {
        "agentQueue" => App::AgentQueue(var(v)?),
        "agentic" => App::Agentic(var(v)?),
        "audioPlayer" => App::AudioPlayer(var(v)?),
        "consumerQueue" => App::ConsumerQueue(var(v)?),
        "inputReader" => App::InputReader(var(v)?),
        "inputReader.v2" => App::InputReaderV2(var(v)?),
        "promptPlayer" => App::PromptPlayer(var(v)?),
        "recording" => App::Recording(var(v)?),
        "simpleMenu" => App::SimpleMenu(var(v)?),
        "speechToText" => App::SpeechToText(var(v)?),
        "switchNode" => App::SwitchNode(var(v)?),
        "textToSpeech" => App::TextToSpeech(var(v)?),
        "transfer" => App::Transfer(var(v)?),
        other => {
            return Err(ManagerError::Decode(format!(
                "unknown application module '{other}'"
            )))
        }
    })
}

/// One raw page of `/api/v2/applications`, decoded item-by-item.
///
/// Stands in for the generated `list_applications`: that returns the internally-tagged
/// [`models::Application`] enum, which fails to deserialize on every real payload (see
/// [`decode_application`]), so the facade reads raw and decodes each item itself.
async fn applications_page(
    cfg: &Configuration,
    page: i32,
    per_page: Option<i32>,
) -> Result<(Vec<models::Application>, i32, i32, Option<i32>), ManagerError> {
    let mut path = format!("/api/v2/applications?page={page}");
    if let Some(max) = per_page {
        path.push_str(&format!("&max={max}"));
    }
    let v = get_json(cfg, &path).await?;
    let items = v
        .get("items")
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default()
        .into_iter()
        .map(decode_application)
        .collect::<Result<Vec<_>, _>>()?;
    let p = v.get("pagination").cloned().unwrap_or(Value::Null);
    let num = |k: &str| p.get(k).and_then(Value::as_i64).map(|n| n as i32);
    Ok((
        items,
        num("pages").unwrap_or(1),
        num("current").unwrap_or(page),
        num("total"),
    ))
}

/// Applications — `/api/v2/applications`, with a nested `local_automations` sub-resource.
pub struct ApplicationsResource {
    pub(crate) cfg: Arc<Configuration>,
    pub(crate) retry: RetryPolicy,
    /// Per-application local automations — `/api/v2/applications/{applicationId}/actions`.
    pub local_automations: LocalAutomationsResource,
}

impl ApplicationsResource {
    /// List all applications (auto-paginated).
    pub async fn list_all(&self) -> Result<Vec<models::Application>, ManagerError> {
        collect_all(
            &self.retry,
            |e| e,
            |page| async move {
                applications_page(self.cfg.as_ref(), page, None)
                    .await
                    .map(|(items, pages, current, _)| (items, pages, current))
            },
        )
        .await
    }

    /// List one page of applications.
    pub async fn list_page(
        &self,
        page: i32,
        per_page: Option<i32>,
    ) -> Result<Page<models::Application>, ManagerError> {
        fetch_page(
            &self.retry,
            |e| e,
            || async move { applications_page(self.cfg.as_ref(), page, per_page).await },
        )
        .await
    }

    /// List all local automations across applications (auto-paginated).
    pub async fn all_local_automations(
        &self,
    ) -> Result<Vec<models::LocalAutomation>, ManagerError> {
        collect_all(&self.retry, map_manager_err, |page| async move {
            let r = local_automation_api::list_all_local_automations(
                self.cfg.as_ref(),
                Some(page),
                None,
            )
            .await?;
            Ok((r.items, r.pagination.pages, r.pagination.current))
        })
        .await
    }

    /// Create an application.
    pub async fn create(
        &self,
        body: models::ApplicationCreateBody,
    ) -> Result<models::ApplicationItemResponse, ManagerError> {
        with_retry(&self.retry, false, || {
            api::create_application(self.cfg.as_ref(), body.clone())
        })
        .await
        .map_err(map_manager_err)
    }

    /// Create an application, reading the response **raw**.
    ///
    /// The typed [`Self::create`] returns [`models::ApplicationItemResponse`], whose derived
    /// `Application` decode fails on every real success payload (the `module` discriminator
    /// artifact — see [`decode_application`]) — misreporting a *completed* create as an error.
    /// Callers that need the created record read raw and lose nothing.
    pub async fn create_raw(&self, body: Value) -> Result<Value, ManagerError> {
        // Non-idempotent create → no retry on ambiguous failures.
        with_retry(&self.retry, false, || {
            post_json_body(self.cfg.as_ref(), "/api/v2/applications", &body)
        })
        .await
    }

    /// Get an application by id.
    ///
    /// Reads the response **raw** and decodes via [`decode_application`], standing in for the
    /// generated `get_application` — whose typed [`models::Application`] return fails to
    /// deserialize on every real payload.
    pub async fn get(&self, id: &str) -> Result<models::ApplicationItemResponse, ManagerError> {
        let path = format!(
            "/api/v2/applications/{}",
            crate::gen::manager::apis::urlencode(id)
        );
        with_retry(&self.retry, true, || async {
            let v = get_json(self.cfg.as_ref(), &path).await?;
            let success = v.get("success").and_then(Value::as_bool).unwrap_or(true);
            let item = v
                .get("item")
                .cloned()
                .ok_or_else(|| ManagerError::Decode("response had no `item`".into()))?;
            Ok(models::ApplicationItemResponse::new(
                decode_application(item)?,
                success,
            ))
        })
        .await
    }

    /// Update an application.
    pub async fn update(
        &self,
        id: &str,
        body: models::ApplicationUpdateBody,
    ) -> Result<models::ApplicationItemResponse, ManagerError> {
        with_retry(&self.retry, false, || {
            api::update_application(self.cfg.as_ref(), id, body.clone())
        })
        .await
        .map_err(map_manager_err)
    }

    /// Delete an application.
    pub async fn delete(&self, id: &str) -> Result<(), ManagerError> {
        with_retry(&self.retry, false, || {
            api::delete_application(self.cfg.as_ref(), id)
        })
        .await
        .map_err(map_manager_err)?;
        Ok(())
    }

    /// Delete several applications by id.
    pub async fn delete_many(
        &self,
        ids: Vec<String>,
    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
        let body = models::DeleteManyApplicationsRequest { ids };
        with_retry(&self.retry, false, || {
            api::delete_many_applications(self.cfg.as_ref(), body.clone())
        })
        .await
        .map_err(map_manager_err)
    }

    /// List the available application modules.
    pub async fn list_modules(&self) -> Result<models::ListModulesResponse, ManagerError> {
        with_retry(&self.retry, true, || api::list_modules(self.cfg.as_ref()))
            .await
            .map_err(map_manager_err)
    }

    /// Dispatch an application's local automations at a given position. `is_async` runs without
    /// waiting for the result.
    pub async fn dispatch(
        &self,
        id: &str,
        position: &str,
        is_async: bool,
        body: models::LocalAutomationDispatch,
    ) -> Result<models::DispatchLocalAutomationsResponse, ManagerError> {
        with_retry(&self.retry, false, || {
            manager_api::dispatch_local_automations(
                self.cfg.as_ref(),
                id,
                position,
                is_async,
                Some(body.clone()),
            )
        })
        .await
        .map_err(map_manager_err)
    }

    /// Clone an application by id.
    pub async fn clone(&self, id: &str) -> Result<models::ApplicationItemResponse, ManagerError> {
        with_retry(&self.retry, false, || {
            api::clone_application(self.cfg.as_ref(), id)
        })
        .await
        .map_err(map_manager_err)
    }

    /// Clone an application by id, reading the response **raw** (same decode caveat as
    /// [`Self::create_raw`]: the typed [`Self::clone`] fails on populated success bodies).
    pub async fn clone_raw(&self, id: &str) -> Result<Value, ManagerError> {
        let path = format!(
            "/api/v2/applications/{}/clone",
            crate::gen::manager::apis::urlencode(id)
        );
        // Non-idempotent action → no retry on ambiguous failures.
        with_retry(&self.retry, false, || post_json(self.cfg.as_ref(), &path)).await
    }

    /// Bulk-update several applications in one request.
    pub async fn bulk_update(
        &self,
        body: models::BulkUpdateApplicationsRequest,
    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
        with_retry(&self.retry, false, || {
            api::bulk_update_applications(self.cfg.as_ref(), body.clone())
        })
        .await
        .map_err(map_manager_err)
    }

    /// List the actions available across applications.
    pub async fn list_app_actions(&self) -> Result<models::ObjectListResponse, ManagerError> {
        with_retry(&self.retry, true, || {
            api::list_application_actions(self.cfg.as_ref(), None, None)
        })
        .await
        .map_err(map_manager_err)
    }

    /// List application errors.
    pub async fn list_errors(&self) -> Result<models::ObjectListResponse, ManagerError> {
        with_retry(&self.retry, true, || {
            api::list_application_errors(self.cfg.as_ref())
        })
        .await
        .map_err(map_manager_err)
    }
}

/// Per-application local automations — `/api/v2/applications/{applicationId}/actions`.
pub struct LocalAutomationsResource {
    pub(crate) cfg: Arc<Configuration>,
    pub(crate) retry: RetryPolicy,
}

impl LocalAutomationsResource {
    /// List all of an application's local automations (auto-paginated).
    pub async fn list_all(
        &self,
        application_id: &str,
    ) -> Result<Vec<models::LocalAutomation>, ManagerError> {
        collect_all(&self.retry, map_manager_err, |page| async move {
            let r = local_automation_api::list_local_automations(
                self.cfg.as_ref(),
                application_id,
                Some(page),
                None,
            )
            .await?;
            Ok((r.items, r.pagination.pages, r.pagination.current))
        })
        .await
    }

    /// Create a local automation on an application.
    pub async fn create(
        &self,
        application_id: &str,
        body: models::RestCreateLocalAutomation,
    ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
        with_retry(&self.retry, false, || {
            local_automation_api::create_local_automation(
                self.cfg.as_ref(),
                application_id,
                body.clone(),
            )
        })
        .await
        .map_err(map_manager_err)
    }

    /// Get a local automation by id.
    pub async fn get(
        &self,
        application_id: &str,
        id: &str,
    ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
        with_retry(&self.retry, true, || {
            local_automation_api::get_local_automation(self.cfg.as_ref(), application_id, id)
        })
        .await
        .map_err(map_manager_err)
    }

    /// Update a local automation.
    pub async fn update(
        &self,
        application_id: &str,
        id: &str,
        body: models::RestUpdateLocalAutomation,
    ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
        with_retry(&self.retry, false, || {
            local_automation_api::update_local_automation(
                self.cfg.as_ref(),
                application_id,
                id,
                body.clone(),
            )
        })
        .await
        .map_err(map_manager_err)
    }

    /// Delete a local automation.
    pub async fn delete(&self, application_id: &str, id: &str) -> Result<(), ManagerError> {
        with_retry(&self.retry, false, || {
            local_automation_api::delete_local_automation(self.cfg.as_ref(), application_id, id)
        })
        .await
        .map_err(map_manager_err)?;
        Ok(())
    }
}