Skip to main content

gap/
gap.rs

1//! Generative Artifact Protocol (GAP) data model — Rust implementation of gap/0.1.
2//!
3//! Three envelope types: `synthesize` (in), `edit` (in), `handle` (out).
4//! Artifact is a standalone content object, not an envelope.
5
6use serde::{Deserialize, Serialize};
7
8pub const PROTOCOL_VERSION: &str = "gap/0.1";
9
10/// Envelope operation name — 2 in, 1 out.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(rename_all = "snake_case")]
13pub enum Name {
14    Synthesize,
15    Edit,
16    Handle,
17}
18
19/// Artifact lifecycle state.
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21#[serde(rename_all = "lowercase")]
22pub enum ArtifactState {
23    Draft,
24    Published,
25    Archived,
26}
27
28/// Envelope metadata.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Meta {
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub format: Option<String>,
33
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub tokens_used: Option<u64>,
36
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub checksum: Option<String>,
39
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub state: Option<ArtifactState>,
42}
43
44/// Wire-format protocol message.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Envelope {
47    pub protocol: String,
48    pub id: String,
49    pub version: u64,
50    pub name: Name,
51    pub meta: Meta,
52    pub content: Vec<serde_json::Value>,
53}
54
55impl Envelope {
56    pub fn from_json(s: &str) -> Result<Self, serde_json::Error> {
57        serde_json::from_str(s)
58    }
59}
60
61// ── Artifact ─────────────────────────────────────────────────────────────
62
63/// The actual content being managed — not an envelope.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct Artifact {
66    pub id: String,
67    pub version: u64,
68    pub format: String,
69    pub body: String,
70}
71
72// ── Synthesize content ───────────────────────────────────────────────────
73
74/// Content item for `name: "synthesize"`.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct SynthesizeContentItem {
77    pub body: String,
78}
79
80// ── Edit content ─────────────────────────────────────────────────────────
81
82/// Target addressing — discriminated union on `type`.
83#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(tag = "type", content = "value")]
85pub enum Target {
86    #[serde(rename = "id")]
87    Id(String),
88
89    #[serde(rename = "pointer")]
90    Pointer(String),
91}
92
93/// Edit operation type.
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
95#[serde(rename_all = "snake_case")]
96pub enum OpType {
97    Replace,
98    InsertBefore,
99    InsertAfter,
100    Delete,
101}
102
103/// A single edit operation (content item for `name: "edit"`).
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct EditOp {
106    pub op: OpType,
107    pub target: Target,
108
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub content: Option<String>,
111}
112
113// ── Handle content ───────────────────────────────────────────────────────
114
115/// Target information included in handle envelopes.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct TargetInfo {
118    pub id: String,
119
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub label: Option<String>,
122
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub accepts: Option<String>,
125}
126
127/// Content item for `name: "handle"` — lightweight artifact reference.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct HandleContentItem {
130    pub id: String,
131    pub version: u64,
132
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub token_count: Option<u64>,
135
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub state: Option<ArtifactState>,
138
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub content: Option<String>,
141
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub targets: Option<Vec<TargetInfo>>,
144}