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}