greentic-runner-host 0.4.72

Host runtime shim for Greentic runner: config, pack loading, activity handling
Documentation
use chrono::{DateTime, Utc};
use serde::Serialize;
use serde_json::{Map, Value, json};

#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ProviderIds {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub team_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub workspace_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conversation_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thread_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub channel_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub event_id: Option<String>,
}

impl ProviderIds {
    pub fn anchor(&self) -> Option<&str> {
        self.thread_id
            .as_deref()
            .or(self.conversation_id.as_deref())
            .or(self.channel_id.as_deref())
    }

    pub fn user(&self) -> Option<&str> {
        self.user_id.as_deref()
    }

    pub fn to_value(&self) -> Value {
        serde_json::to_value(self).unwrap_or(Value::Null)
    }
}

#[derive(Debug, Clone)]
pub struct CanonicalAttachment {
    pub attachment_type: String,
    pub name: Option<String>,
    pub mime: Option<String>,
    pub size: Option<u64>,
    pub url: Option<String>,
    pub data_inline_b64: Option<String>,
}

impl CanonicalAttachment {
    pub fn into_value(self) -> Value {
        json!({
            "type": self.attachment_type,
            "name": self.name,
            "mime": self.mime,
            "size": self.size.unwrap_or(0),
            "url": self.url,
            "data_inline_b64": self.data_inline_b64,
        })
    }
}

#[derive(Debug, Clone)]
pub struct CanonicalButton {
    pub id: String,
    pub title: String,
    pub payload: String,
}

impl CanonicalButton {
    pub fn into_value(self) -> Value {
        json!({
            "id": self.id,
            "title": self.title,
            "payload": self.payload,
        })
    }
}

pub fn canonical_session_key(tenant: &str, provider: &str, ids: &ProviderIds) -> String {
    let user = ids.user().unwrap_or("user");
    let conversation = ids.anchor().or(ids.user()).unwrap_or("conversation");
    format!("{tenant}:{provider}:{conversation}:{user}")
}

#[allow(clippy::too_many_arguments)]
pub fn build_canonical_payload(
    tenant: &str,
    provider: &str,
    provider_ids: &ProviderIds,
    session_key: String,
    scopes: &[String],
    timestamp: DateTime<Utc>,
    locale: Option<String>,
    text: Option<String>,
    attachments: Vec<Value>,
    buttons: Vec<Value>,
    entities: Value,
    metadata: Value,
    mut channel_data: Value,
    raw: Value,
) -> Value {
    let mut payload = Map::with_capacity(13);
    payload.insert("tenant".into(), Value::String(tenant.to_string()));
    payload.insert("provider".into(), Value::String(provider.to_string()));
    payload.insert("provider_ids".into(), provider_ids.to_value());
    payload.insert(
        "session".into(),
        json!({
            "key": session_key,
            "scopes": scopes,
        }),
    );
    payload.insert("timestamp".into(), Value::String(timestamp.to_rfc3339()));
    payload.insert(
        "locale".into(),
        locale.map(Value::String).unwrap_or(Value::Null),
    );
    payload.insert(
        "text".into(),
        text.map(Value::String).unwrap_or(Value::Null),
    );
    payload.insert("attachments".into(), Value::Array(attachments));
    payload.insert("buttons".into(), Value::Array(buttons));
    payload.insert("entities".into(), entities);
    payload.insert("metadata".into(), metadata);

    if channel_data.is_null() {
        channel_data = Value::Object(Default::default());
    }
    payload.insert("channel_data".into(), channel_data);
    payload.insert("raw".into(), raw);
    Value::Object(payload)
}

pub fn empty_entities() -> Value {
    json!({
        "mentions": [],
        "urls": []
    })
}

pub fn default_metadata() -> Value {
    json!({
        "raw_headers": {},
        "ip": Value::Null
    })
}