use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::{
AdapterRole, FailureClass, IntegrationMode, LifecycleEventKind, SCHEMA_VERSION, SupportState,
ValidationError, require_non_empty,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManifestPlacementClass {
PreSession,
PreFrameLeading,
PreFrameTrailing,
ToolResult,
ManualOperator,
}
impl ManifestPlacementClass {
pub const ALL: &'static [Self] = &[
Self::PreSession,
Self::PreFrameLeading,
Self::PreFrameTrailing,
Self::ToolResult,
Self::ManualOperator,
];
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestLifecycleEventSupport {
pub support: SupportState,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub modes: Vec<IntegrationMode>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestPlacementSupport {
pub support: SupportState,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_bytes: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestContextPressure {
pub support: SupportState,
#[serde(skip_serializing_if = "Option::is_none")]
pub evidence: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestReceipts {
pub native: bool,
pub lifeloop_synthesized: bool,
pub receipt_ledger: SupportState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestSessionIdentity {
pub harness_session_id: SupportState,
pub harness_run_id: SupportState,
pub harness_task_id: SupportState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestSessionRename {
pub support: SupportState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestRenewal {
pub reset: ManifestRenewalReset,
pub continuation: ManifestRenewalContinuation,
#[serde(skip_serializing_if = "Option::is_none")]
pub evidence: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestRenewalReset {
pub native: SupportState,
pub wrapper_mediated: SupportState,
pub manual: SupportState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestRenewalContinuation {
pub observation: SupportState,
pub payload_delivery: SupportState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestApprovalSurface {
pub support: SupportState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestTelemetrySource {
pub source: String,
pub support: SupportState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestKnownDegradation {
pub capability: String,
pub previous_support: SupportState,
pub current_support: SupportState,
#[serde(skip_serializing_if = "Option::is_none")]
pub evidence: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AdapterManifest {
pub contract_version: String,
pub adapter_id: String,
pub adapter_version: String,
pub display_name: String,
pub role: AdapterRole,
pub integration_modes: Vec<IntegrationMode>,
pub lifecycle_events: BTreeMap<LifecycleEventKind, ManifestLifecycleEventSupport>,
pub placement: BTreeMap<ManifestPlacementClass, ManifestPlacementSupport>,
pub context_pressure: ManifestContextPressure,
pub receipts: ManifestReceipts,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_identity: Option<ManifestSessionIdentity>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_rename: Option<ManifestSessionRename>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub renewal: Option<ManifestRenewal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approval_surface: Option<ManifestApprovalSurface>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub failure_modes: Vec<FailureClass>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub telemetry_sources: Vec<ManifestTelemetrySource>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub known_degradations: Vec<ManifestKnownDegradation>,
}
impl AdapterManifest {
pub fn validate(&self) -> Result<(), ValidationError> {
if self.contract_version != SCHEMA_VERSION {
return Err(ValidationError::SchemaVersionMismatch {
expected: SCHEMA_VERSION.to_string(),
found: self.contract_version.clone(),
});
}
require_non_empty(&self.adapter_id, "manifest.adapter_id")?;
require_non_empty(&self.adapter_version, "manifest.adapter_version")?;
require_non_empty(&self.display_name, "manifest.display_name")?;
if self.integration_modes.is_empty() {
return Err(ValidationError::InvalidManifest(
"manifest.integration_modes must declare at least one integration mode".into(),
));
}
for deg in &self.known_degradations {
require_non_empty(°.capability, "manifest.known_degradations[].capability")?;
}
for src in &self.telemetry_sources {
require_non_empty(&src.source, "manifest.telemetry_sources[].source")?;
}
if let Some(renewal) = &self.renewal
&& let Some(evidence) = &renewal.evidence
{
require_non_empty(evidence, "manifest.renewal.evidence")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConformanceLevel {
V1Conformance,
PreConformance,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegisteredAdapter {
pub manifest: AdapterManifest,
pub conformance: ConformanceLevel,
}
pub fn manifest_registry() -> Vec<RegisteredAdapter> {
vec![
RegisteredAdapter {
manifest: codex_manifest(),
conformance: ConformanceLevel::V1Conformance,
},
RegisteredAdapter {
manifest: claude_manifest(),
conformance: ConformanceLevel::V1Conformance,
},
RegisteredAdapter {
manifest: hermes_manifest(),
conformance: ConformanceLevel::PreConformance,
},
RegisteredAdapter {
manifest: openclaw_manifest(),
conformance: ConformanceLevel::PreConformance,
},
RegisteredAdapter {
manifest: gemini_manifest(),
conformance: ConformanceLevel::PreConformance,
},
RegisteredAdapter {
manifest: opencode_manifest(),
conformance: ConformanceLevel::PreConformance,
},
]
}
pub fn lookup_manifest(adapter_id: &str) -> Option<RegisteredAdapter> {
manifest_registry()
.into_iter()
.find(|entry| entry.manifest.adapter_id == adapter_id)
}
fn synthesized() -> SupportState {
SupportState::Synthesized
}
fn native() -> SupportState {
SupportState::Native
}
fn unavailable() -> SupportState {
SupportState::Unavailable
}
fn manual() -> SupportState {
SupportState::Manual
}
pub fn codex_manifest() -> AdapterManifest {
let lifecycle_events = BTreeMap::from([
(
LifecycleEventKind::SessionStarting,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::SessionStarted,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::FrameOpening,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::FrameOpened,
ManifestLifecycleEventSupport {
support: synthesized(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ContextPressureObserved,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ContextCompacted,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::FrameEnding,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::FrameEnded,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::SessionEnding,
ManifestLifecycleEventSupport {
support: unavailable(),
modes: Vec::new(),
},
),
(
LifecycleEventKind::SessionEnded,
ManifestLifecycleEventSupport {
support: unavailable(),
modes: Vec::new(),
},
),
(
LifecycleEventKind::SupervisorTick,
ManifestLifecycleEventSupport {
support: unavailable(),
modes: Vec::new(),
},
),
(
LifecycleEventKind::CapabilityDegraded,
ManifestLifecycleEventSupport {
support: synthesized(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ReceiptEmitted,
ManifestLifecycleEventSupport {
support: synthesized(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ReceiptGapDetected,
ManifestLifecycleEventSupport {
support: unavailable(),
modes: Vec::new(),
},
),
]);
let placement = BTreeMap::from([
(
ManifestPlacementClass::PreSession,
ManifestPlacementSupport {
support: native(),
max_bytes: Some(8192),
},
),
(
ManifestPlacementClass::PreFrameLeading,
ManifestPlacementSupport {
support: native(),
max_bytes: Some(8192),
},
),
(
ManifestPlacementClass::PreFrameTrailing,
ManifestPlacementSupport {
support: unavailable(),
max_bytes: None,
},
),
(
ManifestPlacementClass::ToolResult,
ManifestPlacementSupport {
support: unavailable(),
max_bytes: None,
},
),
(
ManifestPlacementClass::ManualOperator,
ManifestPlacementSupport {
support: manual(),
max_bytes: None,
},
),
]);
AdapterManifest {
contract_version: SCHEMA_VERSION.to_string(),
adapter_id: "codex".into(),
adapter_version: "0.1.0".into(),
display_name: "Codex".into(),
role: AdapterRole::PrimaryWorker,
integration_modes: vec![IntegrationMode::NativeHook, IntegrationMode::ManualSkill],
lifecycle_events,
placement,
context_pressure: ManifestContextPressure {
support: native(),
evidence: Some(
"Codex CLI 0.129 exposes PreCompact before context pressure handling and PostCompact after context compacts"
.into(),
),
},
receipts: ManifestReceipts {
native: false,
lifeloop_synthesized: true,
receipt_ledger: unavailable(),
},
session_identity: Some(ManifestSessionIdentity {
harness_session_id: native(),
harness_run_id: synthesized(),
harness_task_id: unavailable(),
}),
session_rename: None,
renewal: None,
approval_surface: None,
failure_modes: vec![FailureClass::TransportError, FailureClass::PayloadTooLarge],
telemetry_sources: Vec::new(),
known_degradations: Vec::new(),
}
}
pub fn claude_manifest() -> AdapterManifest {
let lifecycle_events = BTreeMap::from([
(
LifecycleEventKind::SessionStarting,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::SessionStarted,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::FrameOpening,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::FrameOpened,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ContextPressureObserved,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ContextCompacted,
ManifestLifecycleEventSupport {
support: unavailable(),
modes: Vec::new(),
},
),
(
LifecycleEventKind::FrameEnding,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::FrameEnded,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::SessionEnding,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::SessionEnded,
ManifestLifecycleEventSupport {
support: native(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::SupervisorTick,
ManifestLifecycleEventSupport {
support: unavailable(),
modes: Vec::new(),
},
),
(
LifecycleEventKind::CapabilityDegraded,
ManifestLifecycleEventSupport {
support: synthesized(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ReceiptEmitted,
ManifestLifecycleEventSupport {
support: synthesized(),
modes: vec![IntegrationMode::NativeHook],
},
),
(
LifecycleEventKind::ReceiptGapDetected,
ManifestLifecycleEventSupport {
support: unavailable(),
modes: Vec::new(),
},
),
]);
let placement = BTreeMap::from([
(
ManifestPlacementClass::PreSession,
ManifestPlacementSupport {
support: native(),
max_bytes: Some(16_384),
},
),
(
ManifestPlacementClass::PreFrameLeading,
ManifestPlacementSupport {
support: native(),
max_bytes: Some(16_384),
},
),
(
ManifestPlacementClass::PreFrameTrailing,
ManifestPlacementSupport {
support: unavailable(),
max_bytes: None,
},
),
(
ManifestPlacementClass::ToolResult,
ManifestPlacementSupport {
support: unavailable(),
max_bytes: None,
},
),
(
ManifestPlacementClass::ManualOperator,
ManifestPlacementSupport {
support: manual(),
max_bytes: None,
},
),
]);
AdapterManifest {
contract_version: SCHEMA_VERSION.to_string(),
adapter_id: "claude".into(),
adapter_version: "0.1.0".into(),
display_name: "Claude".into(),
role: AdapterRole::PrimaryWorker,
integration_modes: vec![IntegrationMode::NativeHook],
lifecycle_events,
placement,
context_pressure: ManifestContextPressure {
support: native(),
evidence: Some(
"Claude emits PreCompact and SessionEnd events that map directly to context.pressure_observed"
.into(),
),
},
receipts: ManifestReceipts {
native: false,
lifeloop_synthesized: true,
receipt_ledger: unavailable(),
},
session_identity: Some(ManifestSessionIdentity {
harness_session_id: native(),
harness_run_id: synthesized(),
harness_task_id: unavailable(),
}),
session_rename: None,
renewal: None,
approval_surface: None,
failure_modes: vec![FailureClass::TransportError, FailureClass::PayloadTooLarge],
telemetry_sources: Vec::new(),
known_degradations: Vec::new(),
}
}
pub fn hermes_manifest() -> AdapterManifest {
pre_conformance_reference_adapter_manifest("hermes", "Hermes")
}
pub fn openclaw_manifest() -> AdapterManifest {
pre_conformance_reference_adapter_manifest("openclaw", "OpenClaw")
}
pub fn gemini_manifest() -> AdapterManifest {
pre_conformance_telemetry_only_manifest("gemini", "Gemini")
}
pub fn opencode_manifest() -> AdapterManifest {
pre_conformance_telemetry_only_manifest("opencode", "OpenCode")
}
fn pre_conformance_reference_adapter_manifest(
adapter_id: &str,
display_name: &str,
) -> AdapterManifest {
let lifecycle_events = BTreeMap::from([
(
LifecycleEventKind::SessionStarting,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::ReferenceAdapter],
},
),
(
LifecycleEventKind::SessionStarted,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::ReferenceAdapter],
},
),
(
LifecycleEventKind::FrameOpening,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::ReferenceAdapter],
},
),
(
LifecycleEventKind::FrameEnded,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::ReferenceAdapter],
},
),
(
LifecycleEventKind::SessionEnded,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::ReferenceAdapter],
},
),
]);
let placement = BTreeMap::from([
(
ManifestPlacementClass::PreSession,
ManifestPlacementSupport {
support: SupportState::Partial,
max_bytes: None,
},
),
(
ManifestPlacementClass::PreFrameLeading,
ManifestPlacementSupport {
support: SupportState::Partial,
max_bytes: None,
},
),
(
ManifestPlacementClass::ManualOperator,
ManifestPlacementSupport {
support: SupportState::Manual,
max_bytes: None,
},
),
]);
AdapterManifest {
contract_version: SCHEMA_VERSION.to_string(),
adapter_id: adapter_id.to_string(),
adapter_version: "0.0.1-pre".into(),
display_name: display_name.to_string(),
role: AdapterRole::Worker,
integration_modes: vec![IntegrationMode::ReferenceAdapter],
lifecycle_events,
placement,
context_pressure: ManifestContextPressure {
support: SupportState::Partial,
evidence: None,
},
receipts: ManifestReceipts {
native: false,
lifeloop_synthesized: true,
receipt_ledger: SupportState::Unavailable,
},
session_identity: None,
session_rename: None,
renewal: None,
approval_surface: None,
failure_modes: Vec::new(),
telemetry_sources: Vec::new(),
known_degradations: Vec::new(),
}
}
fn pre_conformance_telemetry_only_manifest(
adapter_id: &str,
display_name: &str,
) -> AdapterManifest {
let lifecycle_events = BTreeMap::from([
(
LifecycleEventKind::SessionStarting,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::TelemetryOnly],
},
),
(
LifecycleEventKind::ContextPressureObserved,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::TelemetryOnly],
},
),
(
LifecycleEventKind::SessionEnded,
ManifestLifecycleEventSupport {
support: SupportState::Partial,
modes: vec![IntegrationMode::TelemetryOnly],
},
),
]);
let placement = BTreeMap::from([(
ManifestPlacementClass::ManualOperator,
ManifestPlacementSupport {
support: SupportState::Manual,
max_bytes: None,
},
)]);
AdapterManifest {
contract_version: SCHEMA_VERSION.to_string(),
adapter_id: adapter_id.to_string(),
adapter_version: "0.0.1-pre".into(),
display_name: display_name.to_string(),
role: AdapterRole::Observer,
integration_modes: vec![IntegrationMode::TelemetryOnly],
lifecycle_events,
placement,
context_pressure: ManifestContextPressure {
support: SupportState::Partial,
evidence: None,
},
receipts: ManifestReceipts {
native: false,
lifeloop_synthesized: true,
receipt_ledger: SupportState::Unavailable,
},
session_identity: None,
session_rename: None,
renewal: None,
approval_surface: None,
failure_modes: Vec::new(),
telemetry_sources: Vec::new(),
known_degradations: Vec::new(),
}
}