use std::collections::BTreeMap;
use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::bundle::BundleError;
use crate::{AgentKind, BundleRelativePath, PathMapping, Scope, SecretRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SchemaVersion {
major: u16,
minor: u16,
}
impl SchemaVersion {
pub const V1: Self = Self { major: 1, minor: 0 };
pub fn new(major: u16, minor: u16) -> Result<Self, BundleError> {
if major != Self::V1.major {
return Err(BundleError::UnsupportedMajor(major));
}
Ok(Self { major, minor })
}
#[must_use]
pub const fn major(self) -> u16 {
self.major
}
#[must_use]
pub const fn minor(self) -> u16 {
self.minor
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct BundleId(Uuid);
impl BundleId {
#[must_use]
pub const fn from_uuid(value: Uuid) -> Self {
Self(value)
}
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl Default for BundleId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FileKind {
Skill,
Mcp,
Instructions,
Command,
Agent,
Hook,
Settings,
Passthrough,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileRecord {
pub kind: FileKind,
pub mode: u32,
pub sha256: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceDescriptor {
pub agent: AgentKind,
pub agent_version: String,
pub scope: Scope,
pub platform: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceEnvironment {
agent_version: String,
scope: Scope,
platform: String,
}
impl SourceEnvironment {
#[must_use]
pub fn new(
agent_version: impl Into<String>,
scope: Scope,
platform: impl Into<String>,
) -> Self {
Self {
agent_version: agent_version.into(),
scope,
platform: platform.into(),
}
}
}
impl SourceDescriptor {
#[must_use]
pub fn new(agent: AgentKind, environment: SourceEnvironment) -> Self {
Self {
agent,
agent_version: environment.agent_version,
scope: environment.scope,
platform: environment.platform,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Manifest {
pub schema: SchemaVersion,
pub bundle_id: BundleId,
pub created_at: Timestamp,
pub source: SourceDescriptor,
pub files: BTreeMap<BundleRelativePath, FileRecord>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub path_mappings: Vec<PathMapping>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub secret_references: Vec<SecretRef>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ManifestIdentity {
schema: SchemaVersion,
bundle_id: BundleId,
created_at: Timestamp,
}
impl ManifestIdentity {
#[must_use]
pub const fn new(schema: SchemaVersion, bundle_id: BundleId, created_at: Timestamp) -> Self {
Self {
schema,
bundle_id,
created_at,
}
}
}
impl Manifest {
#[must_use]
pub fn new(identity: ManifestIdentity, source: SourceDescriptor) -> Self {
Self {
schema: identity.schema,
bundle_id: identity.bundle_id,
created_at: identity.created_at,
source,
files: BTreeMap::new(),
path_mappings: Vec::new(),
secret_references: Vec::new(),
}
}
#[must_use]
pub fn with_path_mappings(mut self, path_mappings: Vec<PathMapping>) -> Self {
self.path_mappings = path_mappings;
self
}
#[must_use]
pub fn with_secret_references(mut self, secret_references: Vec<SecretRef>) -> Self {
self.secret_references = secret_references;
self
}
}