diff --git a/crates/ai-catalog-oci/src/lib.rs b/crates/ai-catalog-oci/src/lib.rs index a08e721..ca26b29 100644 --- a/crates/ai-catalog-oci/src/lib.rs +++ b/crates/ai-catalog-oci/src/lib.rs @@ -6,7 +6,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fs; use std::path::Path; -use ai_catalog::{AiCatalog, CatalogEntry, HostInfo, Publisher, TrustManifest}; +use ai_catalog::{AiCatalog, CatalogEntry, HostInfo, MEDIA_TYPE_CATALOG, Publisher, TrustManifest}; use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2::{Digest as _, Sha256}; @@ -14,7 +14,6 @@ use thiserror::Error; pub const OCI_IMAGE_INDEX_MEDIA_TYPE: &str = "application/vnd.oci.image.index.v1+json"; pub const OCI_IMAGE_MANIFEST_MEDIA_TYPE: &str = "application/vnd.oci.image.manifest.v1+json"; -pub const AI_CATALOG_MEDIA_TYPE: &str = "application/ai-catalog+json"; pub const ENTRY_CONFIG_MEDIA_TYPE: &str = "application/vnd.ai-catalog.entry.config.v1+json"; pub const OCI_LAYOUT_VERSION: &str = "1.0.0"; pub const OCI_REF_NAME_ANNOTATION: &str = "org.opencontainers.image.ref.name"; @@ -43,7 +42,7 @@ pub enum Error { MissingManifest(String), #[error("missing blob for digest '{0}'")] MissingBlob(String), - #[error("OCI index artifactType must be '{AI_CATALOG_MEDIA_TYPE}'")] + #[error("OCI index artifactType must be '{MEDIA_TYPE_CATALOG}'")] UnsupportedIndexArtifactType, #[error("OCI index is missing ai-catalog.specVersion annotation")] MissingSpecVersion, @@ -246,7 +245,7 @@ pub fn pack_catalog(catalog: &AiCatalog) -> Result { index: OciImageIndex { schema_version: 2, media_type: OCI_IMAGE_INDEX_MEDIA_TYPE.to_owned(), - artifact_type: Some(AI_CATALOG_MEDIA_TYPE.to_owned()), + artifact_type: Some(MEDIA_TYPE_CATALOG.to_owned()), manifests: index_descriptors, annotations: catalog_annotations(catalog)?, }, @@ -257,7 +256,7 @@ pub fn pack_catalog(catalog: &AiCatalog) -> Result { } pub fn unpack_catalog(artifacts: &OciArtifactSet) -> Result { - if artifacts.index.artifact_type.as_deref() != Some(AI_CATALOG_MEDIA_TYPE) { + if artifacts.index.artifact_type.as_deref() != Some(MEDIA_TYPE_CATALOG) { return Err(Error::UnsupportedIndexArtifactType); } @@ -633,7 +632,7 @@ fn select_layout_root_descriptor<'a>( .iter() .filter(|descriptor| { descriptor.media_type == OCI_IMAGE_INDEX_MEDIA_TYPE - && descriptor.artifact_type.as_deref() == Some(AI_CATALOG_MEDIA_TYPE) + && descriptor.artifact_type.as_deref() == Some(MEDIA_TYPE_CATALOG) }) .collect(); @@ -858,13 +857,13 @@ mod tests { use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; - use ai_catalog::{parse_file, parse_str}; + use ai_catalog::{MEDIA_TYPE_CATALOG, parse_file, parse_str}; use serde_json::json; use super::{ - AI_CATALOG_MEDIA_TYPE, COSIGN_PUBLIC_KEY_ARTIFACT_TYPE, COSIGN_SIGNATURE_ARTIFACT_TYPE, - ENTRY_CONFIG_MEDIA_TYPE, Error, OCI_IMAGE_INDEX_MEDIA_TYPE, OCI_IMAGE_MANIFEST_MEDIA_TYPE, - OCI_LAYOUT_VERSION, OCI_REF_NAME_ANNOTATION, TRUST_MANIFEST_ARTIFACT_TYPE, + COSIGN_PUBLIC_KEY_ARTIFACT_TYPE, COSIGN_SIGNATURE_ARTIFACT_TYPE, ENTRY_CONFIG_MEDIA_TYPE, + Error, OCI_IMAGE_INDEX_MEDIA_TYPE, OCI_IMAGE_MANIFEST_MEDIA_TYPE, OCI_LAYOUT_VERSION, + OCI_REF_NAME_ANNOTATION, TRUST_MANIFEST_ARTIFACT_TYPE, attach_cosign_verification_artifacts, descriptor_for_bytes, export_layout, import_layout, pack_catalog, unpack_catalog, }; @@ -882,7 +881,7 @@ mod tests { assert_eq!( artifacts.index.artifact_type.as_deref(), - Some(AI_CATALOG_MEDIA_TYPE) + Some(MEDIA_TYPE_CATALOG) ); assert_eq!(artifacts.index.manifests.len(), catalog.entries.len()); assert_eq!(unpacked, catalog); @@ -1043,7 +1042,7 @@ mod tests { assert_eq!(root_index.media_type, OCI_IMAGE_INDEX_MEDIA_TYPE); assert_eq!( root_index.artifact_type.as_deref(), - Some(AI_CATALOG_MEDIA_TYPE) + Some(MEDIA_TYPE_CATALOG) ); assert_eq!(root_index.manifests.len(), catalog.entries.len()); @@ -1150,7 +1149,7 @@ mod tests { assert_eq!( imported.index.artifact_type.as_deref(), - Some(AI_CATALOG_MEDIA_TYPE) + Some(MEDIA_TYPE_CATALOG) ); assert_eq!(unpacked, catalog); diff --git a/crates/ai-catalog-validate/src/lib.rs b/crates/ai-catalog-validate/src/lib.rs index 60b4fc6..dc1c8a5 100644 --- a/crates/ai-catalog-validate/src/lib.rs +++ b/crates/ai-catalog-validate/src/lib.rs @@ -6,8 +6,9 @@ use std::collections::{BTreeMap, HashMap}; use std::sync::LazyLock; use ai_catalog::{ - AiCatalog, Attestation, CatalogEntry, HostInfo, ProvenanceLink, Publisher, Subject, - TrustManifest, TrustSchema, identity_binds_to_entry, identity_domain, publisher_domain, + AiCatalog, Attestation, CatalogEntry, HostInfo, MEDIA_TYPE_CATALOG, ProvenanceLink, Publisher, + Subject, TrustManifest, TrustSchema, identity_binds_to_entry, identity_domain, + publisher_domain, }; use regex::Regex; use time::OffsetDateTime; @@ -248,7 +249,7 @@ fn validate_entry( } } - if entry.entry_type == "application/ai-catalog+json" { + if entry.entry_type == MEDIA_TYPE_CATALOG { if depth >= MAX_NESTING_DEPTH { push_error( errors, diff --git a/crates/ai-catalog/src/lib.rs b/crates/ai-catalog/src/lib.rs index 267a878..037f499 100644 --- a/crates/ai-catalog/src/lib.rs +++ b/crates/ai-catalog/src/lib.rs @@ -4,10 +4,17 @@ mod error; mod identity; +mod media; mod model; pub use error::{Error, Result}; pub use identity::{identity_binds_to_entry, identity_domain, publisher_domain}; +pub use media::{ + MEDIA_TYPE_A2A_AGENT_CARD, MEDIA_TYPE_AGENT_CARD, MEDIA_TYPE_AGENT_PLUGINS_GZIP, + MEDIA_TYPE_AGENT_PLUGINS_ZIP, MEDIA_TYPE_AGENT_SKILLS_GZIP, MEDIA_TYPE_AGENT_SKILLS_JSON, + MEDIA_TYPE_AGENT_SKILLS_MARKDOWN, MEDIA_TYPE_AGENT_SKILLS_ZIP, MEDIA_TYPE_CATALOG, + MEDIA_TYPE_MCP_SERVER_CARD, +}; pub use model::{ AiCatalog, Attestation, CatalogEntry, HostInfo, ProvenanceLink, Publisher, Subject, TrustManifest, TrustSchema, diff --git a/crates/ai-catalog/src/media.rs b/crates/ai-catalog/src/media.rs new file mode 100644 index 0000000..69de84c --- /dev/null +++ b/crates/ai-catalog/src/media.rs @@ -0,0 +1,16 @@ +// Copyright AI-Catalog Contributors (https://github.com/Agent-Card/ai-catalog-rust) +// Copyright AGNTCY Contributors (https://github.com/agntcy) +// SPDX-License-Identifier: Apache-2.0 + +//! Known catalog-entry media types from the AI Catalog specification +//! (). +pub const MEDIA_TYPE_CATALOG: &str = "application/ai-catalog+json"; +pub const MEDIA_TYPE_AGENT_CARD: &str = "application/agent-card+json"; +pub const MEDIA_TYPE_A2A_AGENT_CARD: &str = "application/a2a-agent-card+json"; +pub const MEDIA_TYPE_MCP_SERVER_CARD: &str = "application/mcp-server-card+json"; +pub const MEDIA_TYPE_AGENT_SKILLS_JSON: &str = "application/agent-skills+json"; +pub const MEDIA_TYPE_AGENT_SKILLS_MARKDOWN: &str = "application/agent-skills+md"; +pub const MEDIA_TYPE_AGENT_SKILLS_ZIP: &str = "application/agent-skills+zip"; +pub const MEDIA_TYPE_AGENT_SKILLS_GZIP: &str = "application/agent-skills+gzip"; +pub const MEDIA_TYPE_AGENT_PLUGINS_ZIP: &str = "application/agent-plugins+zip"; +pub const MEDIA_TYPE_AGENT_PLUGINS_GZIP: &str = "application/agent-plugins+gzip"; diff --git a/crates/ai-catalog/src/model.rs b/crates/ai-catalog/src/model.rs index 9cec6c9..4b20dd2 100644 --- a/crates/ai-catalog/src/model.rs +++ b/crates/ai-catalog/src/model.rs @@ -127,7 +127,7 @@ pub struct CatalogEntry { impl CatalogEntry { pub fn is_nested_catalog(&self) -> bool { - self.entry_type == "application/ai-catalog+json" + self.entry_type == crate::MEDIA_TYPE_CATALOG } }