Skip to main content

systemprompt_models/documents/
drive.rs

1//! The bridge drive sync protocol.
2//!
3//! A bridge device mirrors the documents a user can see into a local
4//! folder. It fetches a [`DriveManifest`] once, then polls
5//! [`DriveChanges`] from the manifest's cursor, downloads blobs by
6//! [`ContentHash`], and uploads local edits as a [`DriveCommitMeta`] plus
7//! blob. A commit whose `base_revision` is not the current head is merged
8//! onto the head when it can be ([`DriveCommitResponse::Merged`] names the
9//! merged revision and its content, which the device then pulls) and is
10//! otherwise answered with [`DriveCommitResponse::Conflict`] and never
11//! applied. [`DriveProposals`] lists the open proposals on the user's
12//! documents and the user's own open proposals.
13//!
14//! Copyright (c) systemprompt.io — Business Source License 1.1.
15//! See <https://systemprompt.io> for licensing details.
16
17use chrono::{DateTime, Utc};
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20use systemprompt_identifiers::{
21    ContentHash, DocumentId, DocumentProposalId, DocumentRevisionId, DriveDeviceId, UserId,
22};
23use systemprompt_provider_contracts::documents::DocumentFormat;
24
25use super::access::ActorKind;
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
28pub struct DriveEntry {
29    pub document_id: DocumentId,
30    pub path: String,
31    pub format: DocumentFormat,
32    pub head_revision_id: DocumentRevisionId,
33    pub content_sha256: ContentHash,
34    pub size_bytes: u64,
35    pub updated_at: DateTime<Utc>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
39pub struct DriveManifest {
40    pub generated_at: DateTime<Utc>,
41    pub cursor: String,
42    pub entries: Vec<DriveEntry>,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
46#[serde(tag = "type", rename_all = "snake_case")]
47pub enum DriveChangeKind {
48    Created,
49    Updated,
50    Renamed { from: String },
51    Deleted,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
55pub struct DriveChange {
56    pub cursor: String,
57    pub document_id: DocumentId,
58    pub path: String,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub revision_id: Option<DocumentRevisionId>,
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub content_sha256: Option<ContentHash>,
63    pub kind: DriveChangeKind,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
67pub struct DriveChanges {
68    pub changes: Vec<DriveChange>,
69    pub next_cursor: String,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73pub struct DriveCommitMeta {
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub document_id: Option<DocumentId>,
76    pub path: String,
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub base_revision: Option<DocumentRevisionId>,
79    pub content_sha256: ContentHash,
80    pub size_bytes: u64,
81    pub device_id: DriveDeviceId,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub message: Option<String>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
87#[serde(tag = "status", rename_all = "snake_case")]
88pub enum DriveCommitResponse {
89    Committed {
90        revision_id: DocumentRevisionId,
91    },
92    Merged {
93        revision_id: DocumentRevisionId,
94        content_sha256: ContentHash,
95    },
96    Conflict {
97        head_revision_id: DocumentRevisionId,
98        head_content_sha256: ContentHash,
99        base_revision_id: Option<DocumentRevisionId>,
100    },
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
104#[serde(rename_all = "snake_case")]
105pub enum PresenceState {
106    Viewing,
107    Editing,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
111#[serde(rename_all = "snake_case")]
112pub enum PresenceSource {
113    Bridge,
114    Agent,
115    Web,
116}
117
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
119pub struct PresenceBeacon {
120    pub document_id: DocumentId,
121    pub state: PresenceState,
122    pub source_kind: PresenceSource,
123    pub source_ref: String,
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    // JSON: editor cursor position; the shape belongs to the client's editor.
126    pub cursor: Option<serde_json::Value>,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
130pub struct DriveProposal {
131    pub proposal_id: DocumentProposalId,
132    pub document_id: DocumentId,
133    pub proposer_id: UserId,
134    pub actor_kind: ActorKind,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub message: Option<String>,
137    pub created_at: DateTime<Utc>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
141pub struct DriveProposals {
142    pub proposals: Vec<DriveProposal>,
143}