Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions crates/ai-catalog-oci/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ 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};
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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -246,7 +245,7 @@ pub fn pack_catalog(catalog: &AiCatalog) -> Result<OciArtifactSet> {
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)?,
},
Expand All @@ -257,7 +256,7 @@ pub fn pack_catalog(catalog: &AiCatalog) -> Result<OciArtifactSet> {
}

pub fn unpack_catalog(artifacts: &OciArtifactSet) -> Result<AiCatalog> {
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);
}

Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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,
};
Expand All @@ -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);
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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);

Expand Down
7 changes: 4 additions & 3 deletions crates/ai-catalog-validate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions crates/ai-catalog/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 16 additions & 0 deletions crates/ai-catalog/src/media.rs
Original file line number Diff line number Diff line change
@@ -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
//! (<https://ai-catalog.io/spec/#catalog-entry>).
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";
2 changes: 1 addition & 1 deletion crates/ai-catalog/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down