Skip to main content

roder_api/
workspace_changes.rs

1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use crate::events::{ThreadId, TurnId};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "camelCase")]
8pub enum WorkspaceChangeStatus {
9    Modified,
10    Added,
11    Deleted,
12    Renamed,
13    Untracked,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "camelCase")]
18pub enum WorkspaceChangeSource {
19    VersionControlReconciled,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23#[serde(rename_all = "camelCase")]
24pub enum WorkspaceChangeConfidence {
25    ObservedAfterTool,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29#[serde(rename_all = "camelCase")]
30pub struct WorkspaceObservedFile {
31    pub path: String,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub old_path: Option<String>,
34    pub status: WorkspaceChangeStatus,
35    pub additions: u32,
36    pub deletions: u32,
37    #[serde(default)]
38    pub binary: bool,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42#[serde(rename_all = "camelCase")]
43pub struct WorkspaceChangeObservation {
44    pub id: String,
45    pub thread_id: ThreadId,
46    pub turn_id: TurnId,
47    pub tool_call_id: String,
48    pub tool_name: String,
49    pub source: WorkspaceChangeSource,
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub provider_id: Option<crate::version_control::VcsProviderId>,
52    pub confidence: WorkspaceChangeConfidence,
53    pub files: Vec<WorkspaceObservedFile>,
54    #[serde(with = "time::serde::rfc3339")]
55    pub created_at: OffsetDateTime,
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn version_control_reconciled_observation_round_trips_with_provider_identity() {
64        let observation = WorkspaceChangeObservation {
65            id: "obs-1".to_string(),
66            thread_id: "thread-1".to_string(),
67            turn_id: "turn-1".to_string(),
68            tool_call_id: "tool-1".to_string(),
69            tool_name: "shell".to_string(),
70            source: WorkspaceChangeSource::VersionControlReconciled,
71            provider_id: Some("git".to_string()),
72            confidence: WorkspaceChangeConfidence::ObservedAfterTool,
73            files: vec![WorkspaceObservedFile {
74                path: "src/lib.rs".to_string(),
75                old_path: None,
76                status: WorkspaceChangeStatus::Modified,
77                additions: 2,
78                deletions: 1,
79                binary: false,
80            }],
81            created_at: OffsetDateTime::UNIX_EPOCH,
82        };
83
84        let encoded = serde_json::to_value(&observation).expect("serialize observation");
85        let decoded = serde_json::from_value::<WorkspaceChangeObservation>(encoded)
86            .expect("deserialize observation");
87
88        assert_eq!(decoded, observation);
89    }
90}