hehe-core 0.0.1

Core types, traits and utilities for hehe AI Agent framework
Documentation
use bytes::Bytes;
use camino::Utf8PathBuf;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::error::Result;

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResourceRef {
    #[serde(skip)]
    Inline(Bytes),
    Base64 { data: String },
    File { path: Utf8PathBuf },
    Url { url: Url },
    ContentAddress { hash: String },
}

impl ResourceRef {
    pub fn inline(data: impl Into<Bytes>) -> Self {
        Self::Inline(data.into())
    }

    pub fn base64(data: impl Into<String>) -> Self {
        Self::Base64 { data: data.into() }
    }

    pub fn file(path: impl Into<Utf8PathBuf>) -> Self {
        Self::File { path: path.into() }
    }

    pub fn url(url: Url) -> Self {
        Self::Url { url }
    }

    pub fn content_address(hash: impl Into<String>) -> Self {
        Self::ContentAddress { hash: hash.into() }
    }

    pub fn is_inline(&self) -> bool {
        matches!(self, Self::Inline(_) | Self::Base64 { .. })
    }

    pub fn is_remote(&self) -> bool {
        matches!(self, Self::Url { .. })
    }

    pub fn is_local(&self) -> bool {
        matches!(self, Self::File { .. })
    }
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ResourceMeta {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub media_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub size: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filename: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub checksum: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub encoding: Option<String>,
}

impl ResourceMeta {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_media_type(mut self, media_type: impl Into<String>) -> Self {
        self.media_type = Some(media_type.into());
        self
    }

    pub fn with_size(mut self, size: u64) -> Self {
        self.size = Some(size);
        self
    }

    pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
        self.filename = Some(filename.into());
        self
    }

    pub fn with_checksum(mut self, checksum: impl Into<String>) -> Self {
        self.checksum = Some(checksum.into());
        self
    }
}

#[derive(Clone, Debug)]
pub struct Resource {
    pub reference: ResourceRef,
    pub meta: ResourceMeta,
}

impl Resource {
    pub fn new(reference: ResourceRef) -> Self {
        Self {
            reference,
            meta: ResourceMeta::default(),
        }
    }

    pub fn with_meta(mut self, meta: ResourceMeta) -> Self {
        self.meta = meta;
        self
    }

    pub fn inline(data: impl Into<Bytes>) -> Self {
        Self::new(ResourceRef::inline(data))
    }

    pub fn from_base64(data: impl Into<String>) -> Self {
        Self::new(ResourceRef::base64(data))
    }

    pub fn from_file(path: impl Into<Utf8PathBuf>) -> Self {
        Self::new(ResourceRef::file(path))
    }

    pub fn from_url(url: Url) -> Self {
        Self::new(ResourceRef::url(url))
    }
}

#[async_trait::async_trait]
pub trait ResourceResolver: Send + Sync {
    async fn resolve(&self, resource: &ResourceRef) -> Result<Bytes>;
    async fn resolve_base64(&self, resource: &ResourceRef) -> Result<String>;
    async fn metadata(&self, resource: &ResourceRef) -> Result<ResourceMeta>;
}

#[async_trait::async_trait]
pub trait ResourceStore: Send + Sync {
    async fn store(&self, data: Bytes, meta: ResourceMeta) -> Result<String>;
    async fn get(&self, content_address: &str) -> Result<Option<Bytes>>;
    async fn exists(&self, content_address: &str) -> Result<bool>;
    async fn delete(&self, content_address: &str) -> Result<bool>;
}