Skip to main content

rightkit_agent_engine_protocol/
references.rs

1use serde::{Deserialize, Serialize};
2
3/// Persisted (metadata-only) reference attached to a user turn. Mirrors the
4/// TypeScript `ReferenceMeta` shape used by the desktop composer. **No image
5/// bytes live here** — the desktop keeps the bytes in-memory only; this
6/// record is the transcript-visible card (filename, size, kind, trust) that
7/// the UI persists under `cr.references.v1` and re-attaches on restart.
8///
9/// Wire compatibility: all fields use `#[serde(default)]` and
10/// `skip_serializing_if` so legacy rows (pre-references) deserialize cleanly.
11/// ReferenceMeta is exposed as a standalone type — the desktop owns the
12/// transcript mutation surface in the user-message variant, and Agent 4's
13/// integration pass binds this type into the `PostMessage` body when the
14/// composer forwards references to the engine.
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(rename_all = "camelCase")]
17pub struct ReferenceMeta {
18    /// Stable client-generated id (uuid v4 on the desktop). Reused by the
19    /// transcript's chip strip and the inline resolver; never recast on
20    /// restart so the chip identity survives a page reload.
21    pub id: String,
22    /// `"external_link" | "workspace_file" | "generated_document" | "image"
23    ///  | "artifact" | "change_set"`. Free-form on the wire so a future
24    /// kind can be introduced without breaking older clients; the desktop
25    /// renders unknown kinds as a generic chip.
26    pub kind: String,
27    /// Display target: URL, workspace-relative path, doc id, artifact id, or
28    /// change-set id. Used by `openExternal` / `revealInFolder` / file
29    /// resolver.
30    pub destination: String,
31    /// Optional MIME hint — `"image/png"`, `"text/markdown"`,
32    /// `"application/octet-stream"`, etc.
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub mime: Option<String>,
35    /// Display filename shown on the chip when `kind` is `image`,
36    /// `workspace_file`, or `generated_document`. Optional for
37    /// `external_link` and `change_set` where the destination is the label.
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub filename: Option<String>,
40    /// Best-effort byte count for the original artifact. `None` for
41    /// `external_link` and `change_set` where the size is not knowable up
42    /// front.
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub size_bytes: Option<i64>,
45    /// Provenance + trust. `source` is the producer string
46    /// (`"composer.paste"`, `"workspace_resolver"`, etc.), `createdAt` is the
47    /// ISO timestamp the user added the reference, `trust` is the
48    /// `ContentTrust` label, `contentHash` (when known) is the SHA-256 of the
49    /// original bytes — present for `image` / `workspace_file` /
50    /// `generated_document` once the desktop has read the file.
51    pub provenance: ReferenceProvenance,
52    /// Action affordances the chip exposes. Free-form on the wire; the
53    /// desktop renders the intersection with its known action set so a
54    /// future `download` action sent from a newer sender doesn't break an
55    /// older renderer.
56    #[serde(default, skip_serializing_if = "Vec::is_empty")]
57    pub actions: Vec<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61#[serde(rename_all = "camelCase")]
62pub struct ReferenceProvenance {
63    pub source: String,
64    pub created_at: String,
65    pub trust: String,
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub content_hash: Option<String>,
68}
69
70impl ReferenceMeta {
71    /// Wire-friendly default: an empty record with no actions / no MIME / no
72    /// size. Useful for tests that need a placeholder.
73    pub fn empty(id: &str, kind: &str, destination: &str) -> Self {
74        Self {
75            id: id.to_string(),
76            kind: kind.to_string(),
77            destination: destination.to_string(),
78            mime: None,
79            filename: None,
80            size_bytes: None,
81            provenance: ReferenceProvenance {
82                source: "composer".to_string(),
83                created_at: "1970-01-01T00:00:00Z".to_string(),
84                trust: "user".to_string(),
85                content_hash: None,
86            },
87            actions: Vec::new(),
88        }
89    }
90}