Skip to main content

agent_sdk_core/records/
content.rs

1//! Durable and observable SDK records. Use these DTOs for events, journals, effects,
2//! context, output, and feature evidence. Constructing records is data-only;
3//! persistence, publication, and external actions happen through ports or application
4//! coordinators. This file contains the content portion of that contract.
5//!
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    domain::{
12        AdapterRef, AgentError, ArtifactId, ContentId, DestinationRef, EntityRef, PolicyRef,
13        PrivacyClass, RetentionClass, SourceRef, TrustClass,
14    },
15    error::{AgentErrorKind, RetryClassification},
16};
17
18#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
19#[serde(transparent)]
20/// Carries the artifact version record payload for journal, event, or fixture surfaces.
21/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
22pub struct ArtifactVersion(String);
23
24impl ArtifactVersion {
25    /// Creates a new records::content value with explicit
26    /// caller-provided inputs. This constructor is data-only and
27    /// performs no I/O or external side effects.
28    pub fn new(value: impl Into<String>) -> Self {
29        Self(value.into())
30    }
31
32    /// Returns this value as str. The accessor is side-effect free and
33    /// keeps ownership with the caller.
34    pub fn as_str(&self) -> &str {
35        &self.0
36    }
37}
38
39#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
40#[serde(transparent)]
41/// Carries the content version record payload for journal, event, or fixture surfaces.
42/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
43pub struct ContentVersion(String);
44
45impl ContentVersion {
46    /// Creates a new records::content value with explicit
47    /// caller-provided inputs. This constructor is data-only and
48    /// performs no I/O or external side effects.
49    pub fn new(value: impl Into<String>) -> Self {
50        Self(value.into())
51    }
52
53    /// Returns this value as str. The accessor is side-effect free and
54    /// keeps ownership with the caller.
55    pub fn as_str(&self) -> &str {
56        &self.0
57    }
58}
59
60#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
61#[serde(rename_all = "snake_case")]
62/// Enumerates the finite content kind cases.
63/// Serialized names are part of the SDK contract; update fixtures when variants change.
64pub enum ContentKind {
65    /// Use this variant when the contract needs to represent text; selecting it has no side effect by itself.
66    Text,
67    /// Use this variant when the contract needs to represent document; selecting it has no side effect by itself.
68    Document,
69    /// Use this variant when the contract needs to represent image; selecting it has no side effect by itself.
70    Image,
71    /// Use this variant when the contract needs to represent audio; selecting it has no side effect by itself.
72    Audio,
73    /// Use this variant when the contract needs to represent video; selecting it has no side effect by itself.
74    Video,
75    /// Use this variant when the contract needs to represent file; selecting it has no side effect by itself.
76    File,
77    /// Use this variant when the contract needs to represent tool result; selecting it has no side effect by itself.
78    ToolResult,
79    /// Use this variant when the contract needs to represent schema; selecting it has no side effect by itself.
80    Schema,
81    /// Use this variant when the contract needs to represent stdout; selecting it has no side effect by itself.
82    Stdout,
83    /// Use this variant when the contract needs to represent stderr; selecting it has no side effect by itself.
84    Stderr,
85    /// Use this variant when the contract needs to represent output payload; selecting it has no side effect by itself.
86    OutputPayload,
87    /// Use this variant when the contract needs to represent memory record; selecting it has no side effect by itself.
88    MemoryRecord,
89    /// Use this variant when the contract needs to represent external; selecting it has no side effect by itself.
90    External,
91}
92
93#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
94#[serde(rename_all = "snake_case")]
95/// Enumerates the finite content scope cases.
96/// Serialized names are part of the SDK contract; update fixtures when variants change.
97pub enum ContentScope {
98    /// Use this variant when the contract needs to represent run; selecting it has no side effect by itself.
99    Run,
100    /// Use this variant when the contract needs to represent session; selecting it has no side effect by itself.
101    Session,
102    /// Use this variant when the contract needs to represent host workspace; selecting it has no side effect by itself.
103    HostWorkspace,
104    /// Use this variant when the contract needs to represent external; selecting it has no side effect by itself.
105    External,
106    /// Use this variant when the contract needs to represent persistent store; selecting it has no side effect by itself.
107    PersistentStore,
108}
109
110#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
111/// Carries the artifact ref record payload for journal, event, or fixture surfaces.
112/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
113pub struct ArtifactRef {
114    /// Stable artifact id used for typed lineage, lookup, or dedupe.
115    pub artifact_id: ArtifactId,
116    /// Version string for this capability, package, or protocol surface.
117    /// Use it for compatibility checks during package or adapter resolution.
118    pub version: ArtifactVersion,
119    /// Scope used by this record or request.
120    pub scope: ContentScope,
121    /// Typed storage ref reference. Resolving or executing it is a separate
122    /// policy-gated step.
123    pub storage_ref: AdapterRef,
124    /// Optional mime value.
125    /// When absent, callers should use the documented default or skip that optional behavior.
126    pub mime: Option<String>,
127    /// size bytes used for bounds checks, summaries, or truncation evidence.
128    pub size_bytes: Option<u64>,
129    /// Stable hash for the bytes or canonical payload used for stale checks
130    /// and fingerprints.
131    pub content_hash: Option<String>,
132    /// Typed producer ref reference. Resolving or executing it is a separate
133    /// policy-gated step.
134    pub producer_ref: EntityRef,
135    /// Typed source reference that records where this item originated.
136    pub source_ref: SourceRef,
137    /// Typed destination reference that records where this item is being sent
138    /// or projected.
139    pub destination_ref: Option<DestinationRef>,
140    /// Privacy class used for projection, telemetry, and raw-content access
141    /// decisions.
142    pub privacy_class: PrivacyClass,
143    /// Retention class used by hosts and sinks when storing or exporting this
144    /// item.
145    pub retention_class: RetentionClass,
146    /// Trust class used when deciding whether context or capabilities may be
147    /// admitted.
148    pub trust_class: TrustClass,
149    /// Redacted human-readable summary safe for events, telemetry, and logs.
150    pub redacted_summary: String,
151}
152
153impl ArtifactRef {
154    /// Creates a new records::content value with explicit
155    /// caller-provided inputs. This constructor is data-only and
156    /// performs no I/O or external side effects.
157    pub fn new(
158        artifact_id: ArtifactId,
159        version: ArtifactVersion,
160        scope: ContentScope,
161        storage_ref: AdapterRef,
162        producer_ref: EntityRef,
163        source_ref: SourceRef,
164        redacted_summary: impl Into<String>,
165    ) -> Self {
166        Self {
167            artifact_id,
168            version,
169            scope,
170            storage_ref,
171            mime: None,
172            size_bytes: None,
173            content_hash: None,
174            producer_ref,
175            source_ref,
176            destination_ref: None,
177            privacy_class: PrivacyClass::ContentRefsOnly,
178            retention_class: RetentionClass::RunScoped,
179            trust_class: TrustClass::HostProvided,
180            redacted_summary: redacted_summary.into(),
181        }
182    }
183}
184
185#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
186/// Carries the content ref record payload for journal, event, or fixture surfaces.
187/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
188pub struct ContentRef {
189    /// Stable content id used for typed lineage, lookup, or dedupe.
190    pub content_id: ContentId,
191    /// Version string for this capability, package, or protocol surface.
192    /// Use it for compatibility checks during package or adapter resolution.
193    pub version: ContentVersion,
194    /// Kind/category for this record, capability, event, or detected
195    /// resource.
196    pub kind: ContentKind,
197    /// Typed artifact ref reference. Resolving or executing it is a separate
198    /// policy-gated step.
199    pub artifact_ref: Option<ArtifactRef>,
200    /// Scope used by this record or request.
201    pub scope: ContentScope,
202    /// Typed producer ref reference. Resolving or executing it is a separate
203    /// policy-gated step.
204    pub producer_ref: EntityRef,
205    /// Typed source reference that records where this item originated.
206    pub source_ref: SourceRef,
207    /// Typed destination reference that records where this item is being sent
208    /// or projected.
209    pub destination_ref: Option<DestinationRef>,
210    /// Optional mime value.
211    /// When absent, callers should use the documented default or skip that optional behavior.
212    pub mime: Option<String>,
213    /// size bytes used for bounds checks, summaries, or truncation evidence.
214    pub size_bytes: Option<u64>,
215    /// Stable hash for the bytes or canonical payload used for stale checks
216    /// and fingerprints.
217    pub content_hash: Option<String>,
218    /// Privacy class used for projection, telemetry, and raw-content access
219    /// decisions.
220    pub privacy_class: PrivacyClass,
221    /// Retention class used by hosts and sinks when storing or exporting this
222    /// item.
223    pub retention_class: RetentionClass,
224    /// Trust class used when deciding whether context or capabilities may be
225    /// admitted.
226    pub trust_class: TrustClass,
227    /// Typed resolver ref reference. Resolving or executing it is a separate
228    /// policy-gated step.
229    pub resolver_ref: AdapterRef,
230    /// Redacted human-readable summary safe for events, telemetry, and logs.
231    pub redacted_summary: String,
232}
233
234impl ContentRef {
235    /// Creates a new records::content value with explicit
236    /// caller-provided inputs. This constructor is data-only and
237    /// performs no I/O or external side effects.
238    #[expect(
239        clippy::too_many_arguments,
240        reason = "ContentRef::new is the explicit durable DTO constructor; a builder would be a separate public API ergonomics pass"
241    )]
242    pub fn new(
243        content_id: ContentId,
244        version: ContentVersion,
245        kind: ContentKind,
246        scope: ContentScope,
247        producer_ref: EntityRef,
248        source_ref: SourceRef,
249        resolver_ref: AdapterRef,
250        redacted_summary: impl Into<String>,
251    ) -> Self {
252        Self {
253            content_id,
254            version,
255            kind,
256            artifact_ref: None,
257            scope,
258            producer_ref,
259            source_ref,
260            destination_ref: None,
261            mime: None,
262            size_bytes: None,
263            content_hash: None,
264            privacy_class: PrivacyClass::ContentRefsOnly,
265            retention_class: RetentionClass::RunScoped,
266            trust_class: TrustClass::HostProvided,
267            resolver_ref,
268            redacted_summary: redacted_summary.into(),
269        }
270    }
271
272    /// Returns summary for default events for callers that need to inspect the contract state.
273    /// This is data-only and does not perform I/O, call host ports, append journals, publish
274    /// events, or start processes.
275    pub fn summary_for_default_events(&self) -> &str {
276        &self.redacted_summary
277    }
278
279    /// Returns whether provider visible without context admission applies for this state.
280    /// This is data-only and does not perform I/O, call host ports, append journals, publish
281    /// events, or start processes.
282    pub fn provider_visible_without_context_admission(&self) -> bool {
283        false
284    }
285}
286
287#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
288#[serde(rename_all = "snake_case")]
289/// Enumerates the finite content resolution purpose cases.
290/// Serialized names are part of the SDK contract; update fixtures when variants change.
291pub enum ContentResolutionPurpose {
292    /// Use this variant when the contract needs to represent context projection; selecting it has no side effect by itself.
293    ContextProjection,
294    /// Use this variant when the contract needs to represent output validation; selecting it has no side effect by itself.
295    OutputValidation,
296    /// Use this variant when the contract needs to represent output delivery; selecting it has no side effect by itself.
297    OutputDelivery,
298    /// Use this variant when the contract needs to represent replay; selecting it has no side effect by itself.
299    Replay,
300    /// Use this variant when the contract needs to represent host inspection; selecting it has no side effect by itself.
301    HostInspection,
302}
303
304#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
305#[serde(rename_all = "snake_case")]
306/// Enumerates the finite retention use cases.
307/// Serialized names are part of the SDK contract; update fixtures when variants change.
308pub enum RetentionUse {
309    /// Use this variant when the contract needs to represent read only; selecting it has no side effect by itself.
310    ReadOnly,
311    /// Use this variant when the contract needs to represent provider projection; selecting it has no side effect by itself.
312    ProviderProjection,
313    /// Use this variant when the contract needs to represent validation; selecting it has no side effect by itself.
314    Validation,
315    /// Use this variant when the contract needs to represent delivery; selecting it has no side effect by itself.
316    Delivery,
317    /// Use this variant when the contract needs to represent replay; selecting it has no side effect by itself.
318    Replay,
319}
320
321#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
322#[serde(rename_all = "snake_case")]
323/// Enumerates the finite missing content policy cases.
324/// Serialized names are part of the SDK contract; update fixtures when variants change.
325pub enum MissingContentPolicy {
326    /// Use this variant when the contract needs to represent fail; selecting it has no side effect by itself.
327    Fail,
328    /// Use this variant when the contract needs to represent recoverable replay gap; selecting it has no side effect by itself.
329    RecoverableReplayGap,
330    /// Use this variant when the contract needs to represent omit with projection audit; selecting it has no side effect by itself.
331    OmitWithProjectionAudit,
332    /// Use this variant when the contract needs to represent request host repair; selecting it has no side effect by itself.
333    RequestHostRepair,
334}
335
336#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
337/// Carries the content resolution policy record payload for journal, event, or fixture surfaces.
338/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
339pub struct ContentResolutionPolicy {
340    /// Typed caller ref reference. Resolving or executing it is a separate
341    /// policy-gated step.
342    pub caller_ref: EntityRef,
343    /// Typed destination reference that records where this item is being sent
344    /// or projected.
345    pub destination_ref: DestinationRef,
346    /// Purpose used by this record or request.
347    pub purpose: ContentResolutionPurpose,
348    /// Privacy classification for the value.
349    /// Projection, telemetry, and delivery paths use it to enforce redaction and retention.
350    pub allowed_privacy_classes: Vec<PrivacyClass>,
351    /// Maximum byte budget the caller requested before truncation or summary
352    /// behavior is applied.
353    pub max_bytes: u64,
354    /// Boolean policy/capability flag for whether require hash match is
355    /// enabled.
356    pub require_hash_match: bool,
357    /// Retention class for referenced content or records.
358    /// Stores and telemetry sinks use it to decide how long evidence may be kept.
359    pub retention_use: RetentionUse,
360    /// On missing used by this record or request.
361    pub on_missing: MissingContentPolicy,
362    /// Boolean policy/capability flag for whether allow raw content is
363    /// enabled.
364    pub allow_raw_content: bool,
365    /// Policy references that govern admission, projection, execution, or
366    /// delivery.
367    pub policy_refs: Vec<PolicyRef>,
368}
369
370impl ContentResolutionPolicy {
371    /// Builds the redacted context value.
372    /// This is data construction and performs no I/O, journal append, event publication, or
373    /// process work.
374    pub fn redacted_context(
375        caller_ref: EntityRef,
376        destination_ref: DestinationRef,
377        policy_ref: PolicyRef,
378    ) -> Self {
379        Self {
380            caller_ref,
381            destination_ref,
382            purpose: ContentResolutionPurpose::ContextProjection,
383            allowed_privacy_classes: vec![PrivacyClass::Public, PrivacyClass::ContentRefsOnly],
384            max_bytes: 0,
385            require_hash_match: true,
386            retention_use: RetentionUse::ProviderProjection,
387            on_missing: MissingContentPolicy::Fail,
388            allow_raw_content: false,
389            policy_refs: vec![policy_ref],
390        }
391    }
392
393    /// Builds the raw context value with the documented defaults.
394    /// This is data-only and does not perform I/O, call host ports, append journals, publish
395    /// events, or start processes.
396    pub fn raw_context(
397        caller_ref: EntityRef,
398        destination_ref: DestinationRef,
399        policy_ref: PolicyRef,
400        max_bytes: u64,
401    ) -> Self {
402        let mut policy = Self::redacted_context(caller_ref, destination_ref, policy_ref);
403        policy.allowed_privacy_classes = vec![PrivacyClass::Public, PrivacyClass::ContentRefsOnly];
404        policy.max_bytes = max_bytes;
405        policy.allow_raw_content = true;
406        policy
407    }
408}
409
410#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
411/// Carries the content resolve request record payload for journal, event, or fixture surfaces.
412/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
413pub struct ContentResolveRequest {
414    /// Content reference where payload bytes or structured tool output are
415    /// stored.
416    pub content_ref: ContentRef,
417    /// Version string for this capability, package, or protocol surface.
418    /// Use it for compatibility checks during package or adapter resolution.
419    pub requested_version: ContentVersion,
420}
421
422impl ContentResolveRequest {
423    /// Creates a new records::content value with explicit
424    /// caller-provided inputs. This constructor is data-only and
425    /// performs no I/O or external side effects.
426    pub fn new(content_ref: ContentRef) -> Self {
427        Self {
428            requested_version: content_ref.version.clone(),
429            content_ref,
430        }
431    }
432}
433
434#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
435/// Carries the resolved content record payload for journal, event, or fixture surfaces.
436/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
437pub struct ResolvedContent {
438    /// Content reference where payload bytes or structured tool output are
439    /// stored.
440    pub content_ref: ContentRef,
441    /// Optional mime value.
442    /// When absent, callers should use the documented default or skip that optional behavior.
443    pub mime: Option<String>,
444    /// Byte size or byte limit for bytes.
445    /// Use it to enforce bounded reads, writes, summaries, or parser output.
446    pub bytes: Option<Vec<u8>>,
447    /// Redacted human-readable summary safe for events, telemetry, and logs.
448    pub redacted_summary: String,
449    /// Policy references that govern admission, projection, execution, or
450    /// delivery.
451    pub policy_refs: Vec<PolicyRef>,
452    /// Raw content or raw-content control for this value.
453    /// Use it only when policy explicitly allows raw content capture or delivery.
454    pub raw_content_included: bool,
455}
456
457impl ResolvedContent {
458    /// Returns an updated value with redacted configured.
459    /// This is data-only and does not perform I/O, call host ports, append journals, publish
460    /// events, or start processes.
461    pub fn redacted(content_ref: ContentRef, policy_refs: Vec<PolicyRef>) -> Self {
462        Self {
463            mime: content_ref.mime.clone(),
464            redacted_summary: content_ref.redacted_summary.clone(),
465            content_ref,
466            bytes: None,
467            policy_refs,
468            raw_content_included: false,
469        }
470    }
471}
472
473#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
474#[serde(rename_all = "snake_case")]
475/// Enumerates the finite content resolution error kind cases.
476/// Serialized names are part of the SDK contract; update fixtures when variants change.
477pub enum ContentResolutionErrorKind {
478    /// Use this variant when the contract needs to represent missing; selecting it has no side effect by itself.
479    Missing,
480    /// Use this variant when the contract needs to represent expired; selecting it has no side effect by itself.
481    Expired,
482    /// Use this variant when the contract needs to represent permission denied; selecting it has no side effect by itself.
483    PermissionDenied,
484    /// Use this variant when the contract needs to represent storage unavailable; selecting it has no side effect by itself.
485    StorageUnavailable,
486    /// Use this variant when the contract needs to represent version mismatch; selecting it has no side effect by itself.
487    VersionMismatch,
488    /// Use this variant when the contract needs to represent hash mismatch; selecting it has no side effect by itself.
489    HashMismatch,
490    /// Use this variant when the contract needs to represent unsupported mime; selecting it has no side effect by itself.
491    UnsupportedMime,
492    /// Use this variant when the contract needs to represent raw content not allowed; selecting it has no side effect by itself.
493    RawContentNotAllowed,
494    /// Use this variant when the contract needs to represent max bytes exceeded; selecting it has no side effect by itself.
495    MaxBytesExceeded,
496}
497
498#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
499/// Carries the content resolution error record payload for journal, event, or fixture surfaces.
500/// Creating or cloning it only preserves serialized SDK state; append, publish, replay, or export effects are documented on the runtime and port methods that store it.
501pub struct ContentResolutionError {
502    /// Kind/category for this record, capability, event, or detected
503    /// resource.
504    pub kind: ContentResolutionErrorKind,
505    /// Content reference where payload bytes or structured tool output are
506    /// stored.
507    pub content_ref: Box<ContentRef>,
508    /// Policy references that govern admission, projection, execution, or
509    /// delivery.
510    pub policy_refs: Vec<PolicyRef>,
511    /// Redacted human-readable summary safe for events, telemetry, and logs.
512    pub redacted_summary: String,
513}
514
515impl ContentResolutionError {
516    /// Converts this value into agent error data.
517    /// This is data-only and does not perform I/O, call host ports, append journals, publish
518    /// events, or start processes.
519    pub fn to_agent_error(&self) -> AgentError {
520        let retry = match self.kind {
521            ContentResolutionErrorKind::Missing => RetryClassification::RepairNeeded,
522            ContentResolutionErrorKind::StorageUnavailable => RetryClassification::Retryable,
523            _ => RetryClassification::NotRetryable,
524        };
525        AgentError::new(
526            AgentErrorKind::ProjectionFailure,
527            retry,
528            format!("content resolution failed: {:?}", self.kind),
529        )
530    }
531}
532
533impl fmt::Display for ContentResolutionError {
534    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
535        write!(formatter, "content resolution failed: {:?}", self.kind)
536    }
537}
538
539impl std::error::Error for ContentResolutionError {}
540
541/// Builds the resolution error value.
542/// This is data construction and performs no I/O, journal append, event publication, or process
543pub(crate) fn resolution_error(
544    kind: ContentResolutionErrorKind,
545    content_ref: ContentRef,
546    policy_refs: Vec<PolicyRef>,
547) -> ContentResolutionError {
548    ContentResolutionError {
549        kind,
550        redacted_summary: content_ref.redacted_summary.clone(),
551        content_ref: Box::new(content_ref),
552        policy_refs,
553    }
554}