Skip to main content

open_agent_profile/
lib.rs

1//! Native Rust support for Open Agent Profile (OAP) 1.0.
2//!
3//! Includes YAML, JSON, and Markdown encodings; schema and security validation;
4//! RFC 8785 identities; composition; policy narrowing; prompt rendering; and
5//! atomic, retention-aware state delta application.
6
7#![warn(missing_docs)]
8
9mod canonical;
10mod composition;
11mod delta;
12mod parse;
13mod policy;
14mod render;
15mod validate;
16
17pub use canonical::{CanonicalError, canonical_json, profile_digest, profile_digests, spec_digest};
18pub use composition::{
19    CompositionError, ProfileReference, merge_profile_values, resolve_composition,
20};
21pub use delta::{
22    ApplyError, ApplyOptions, ConflictError, apply_delta, serialize, write_atomically,
23};
24pub use parse::{OapFormat, ParseError, load, parse};
25pub use policy::{
26    EffectiveTools, PermissionDecision, intersect_tools, narrow_decision, narrow_permission_map,
27};
28pub use render::{RenderError, RenderOptions, render_system_prompt, substitute_variables};
29pub use validate::{escapes_workspace, validate, validate_path};
30
31use serde::{Deserialize, Serialize};
32use serde_json::{Map, Value};
33
34/// OAP specification version implemented by this crate.
35pub const OAP_VERSION: &str = "1.0";
36/// Version of this Rust support library.
37pub const SUPPORT_VERSION: &str = "1.0.4";
38/// A parsed OAP document represented as a JSON-compatible object.
39pub type Document = Map<String, Value>;
40/// A parsed `AgentProfile` document.
41pub type AgentProfile = Document;
42/// A parsed `AgentStateDelta` document.
43pub type AgentStateDelta = Document;
44
45/// One validation issue located by JSON Pointer.
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47pub struct Issue {
48    /// JSON Pointer locating the affected value.
49    pub pointer: String,
50    /// Human-readable explanation of the issue.
51    pub message: String,
52}
53
54/// Canonical identities for a full profile and its immutable specification.
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct Digests {
57    /// SHA-256 identity of the full canonical profile.
58    pub profile: String,
59    /// SHA-256 identity of the canonical `spec` member.
60    pub spec: String,
61}
62
63/// Complete schema, semantic, and security validation result.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ValidationReport {
66    /// Parsed OAP document kind.
67    pub kind: String,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    /// Parsed document, absent only when loading or parsing failed.
70    pub document: Option<Document>,
71    /// Validation failures.
72    pub errors: Vec<Issue>,
73    /// Non-fatal validation observations.
74    pub warnings: Vec<Issue>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    /// Canonical identities for a valid `AgentProfile`.
77    pub digests: Option<Digests>,
78    /// Whether validation completed without errors.
79    pub ok: bool,
80}
81
82/// A requested policy value that was narrowed by an effective ceiling.
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct Adjustment {
85    /// Logical policy field that changed.
86    pub field: String,
87    /// Value requested by the profile or harness.
88    pub requested: Value,
89    /// Effective narrowed value.
90    pub effective: Value,
91    /// Human-readable explanation of the narrowing.
92    pub reason: String,
93}
94
95/// Successful result of applying an agent state delta.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct DeltaApplication {
98    /// New profile value; the input profile is never mutated.
99    pub profile: AgentProfile,
100    /// Retention and application warnings.
101    pub warnings: Vec<String>,
102    /// Capability-widening proposals left for human review.
103    pub pending_proposals: Vec<Document>,
104}
105
106pub(crate) fn object(value: Option<&Value>) -> &Map<String, Value> {
107    value.and_then(Value::as_object).unwrap_or_else(|| {
108        static EMPTY: std::sync::LazyLock<Map<String, Value>> = std::sync::LazyLock::new(Map::new);
109        &EMPTY
110    })
111}
112
113pub(crate) fn strings(value: Option<&Value>) -> Vec<String> {
114    value
115        .and_then(Value::as_array)
116        .into_iter()
117        .flatten()
118        .filter_map(Value::as_str)
119        .map(str::to_owned)
120        .collect()
121}