Skip to main content

tirea_contract/thread/
changeset.rs

1//! Shared persistence change-set types shared by runtime and storage.
2
3use crate::thread::{Message, Thread};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::sync::Arc;
7use tirea_state::TrackedPatch;
8
9/// Monotonically increasing version for optimistic concurrency.
10pub type Version = u64;
11
12/// Reason for a checkpoint (delta).
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14pub enum CheckpointReason {
15    UserMessage,
16    AssistantTurnCommitted,
17    ToolResultsCommitted,
18    RunFinished,
19}
20
21/// An incremental change to a thread produced by a single step.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ThreadChangeSet {
24    /// Which run produced this delta.
25    pub run_id: String,
26    /// Parent run (for sub-agent deltas).
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub parent_run_id: Option<String>,
29    /// Why this delta was created.
30    pub reason: CheckpointReason,
31    /// New messages appended in this step.
32    pub messages: Vec<Arc<Message>>,
33    /// New patches appended in this step.
34    pub patches: Vec<TrackedPatch>,
35    /// If `Some`, a full state snapshot was taken (replaces base state).
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub snapshot: Option<Value>,
38}
39
40impl ThreadChangeSet {
41    /// Build a `ThreadChangeSet` from explicit delta components.
42    pub fn from_parts(
43        run_id: impl Into<String>,
44        parent_run_id: Option<String>,
45        reason: CheckpointReason,
46        messages: Vec<Arc<Message>>,
47        patches: Vec<TrackedPatch>,
48        snapshot: Option<Value>,
49    ) -> Self {
50        Self {
51            run_id: run_id.into(),
52            parent_run_id,
53            reason,
54            messages,
55            patches,
56            snapshot,
57        }
58    }
59
60    /// Apply this delta to a thread in place.
61    pub fn apply_to(&self, thread: &mut Thread) {
62        if let Some(ref snapshot) = self.snapshot {
63            thread.state = snapshot.clone();
64            thread.patches.clear();
65        }
66
67        thread.messages.extend(self.messages.iter().cloned());
68        thread.patches.extend(self.patches.iter().cloned());
69    }
70}