OXYGENHUB
# c7161ec1a607 spectra / crates / spectra-api / src / lib.rs
7.0 KiB · Rust · last changed Briefs and Radar's Constellation (#29, #30); spec Draft 0.6 1 day ago source blame history raw
1
//! The JSON API's types (spec §18), with serde and JSON Schema derives, as
2
//! `hub-api` is for OxygenHub. `make ts` writes `ts/index.d.ts` from them
3
//! for spectra-vscode and spectrahub-vscode, so the extensions can never
4
//! drift from the server.
5
6
use schemars::JsonSchema;
7
use serde::{Deserialize, Serialize};
8
9
/// An error, as RFC 7807 has it.
10
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
11
pub struct Problem {
12
    #[serde(rename = "type")]
13
    pub kind: String,
14
    pub title: String,
15
    pub status: u16,
16
    #[serde(skip_serializing_if = "Option::is_none")]
17
    pub detail: Option<String>,
18
}
19
20
/// `GET /api/v1/version`.
21
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
22
pub struct Version {
23
    pub version: String,
24
    pub ledger_format: u32,
25
    pub mcp_protocol_versions: Vec<String>,
26
}
27
28
/// An agent and its owner (spec §8.1).
29
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
30
pub struct Agent {
31
    pub name: String,
32
    /// `claude-oxygen · Andy McCall's agent`.
33
    pub display: String,
34
    pub owner: String,
35
    pub key: String,
36
    pub kind: String,
37
    pub project: String,
38
    /// online, busy, idle, blocked, paused or offline.
39
    pub presence: String,
40
    pub status: Option<String>,
41
    pub disabled: bool,
42
    /// `ok` while the owner's identity delegates to the key, else why not.
43
    #[serde(default)]
44
    pub state: String,
45
    /// When the owner's delegation to the key ends, in ms.
46
    #[serde(default)]
47
    pub delegation_expires: Option<u64>,
48
}
49
50
/// A project (spec §23.1).
51
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
52
pub struct Project {
53
    pub name: String,
54
    /// A folder, if the work has one; Spectra never reads it.
55
    pub path: Option<String>,
56
    #[serde(default)]
57
    pub description: String,
58
    /// A link to the work, if any.
59
    pub url: Option<String>,
60
    pub depends_on: Vec<String>,
61
    pub depended_on_by: Vec<String>,
62
    /// Its agents that are not disabled.
63
    #[serde(default)]
64
    pub agents: Vec<String>,
65
}
66
67
/// A channel (spec §10.1).
68
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
69
pub struct Channel {
70
    pub id: String,
71
    pub name: String,
72
    /// `#general`, `#oxygen`, or `@a+b` for a direct channel.
73
    pub display: String,
74
    pub kind: String,
75
    pub purpose: Option<String>,
76
    pub messages: i64,
77
}
78
79
/// A message (spec §10.3); a redacted one's body says so.
80
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
81
pub struct Message {
82
    pub id: String,
83
    pub channel: String,
84
    pub reply_to: Option<String>,
85
    pub author: String,
86
    pub author_display: String,
87
    pub kind: String,
88
    pub body: String,
89
    pub mentions: Vec<String>,
90
    pub refs: Vec<String>,
91
    pub at_ms: i64,
92
    pub replies: i64,
93
    pub redacted: bool,
94
}
95
96
/// A page of messages, oldest first; `next` is the cursor for the next page.
97
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
98
pub struct Messages {
99
    pub channel: String,
100
    pub items: Vec<Message>,
101
    pub next: Option<String>,
102
}
103
104
/// `POST /api/v1/channels/{name}/messages`.
105
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
106
pub struct NewMessage {
107
    pub body: String,
108
    #[serde(default)]
109
    pub reply_to: Option<String>,
110
    #[serde(default)]
111
    pub kind: Option<String>,
112
}
113
114
/// What a post did.
115
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
116
pub struct Posted {
117
    pub id: String,
118
    pub channel: String,
119
    /// True when it was dropped as a repeat of `id` within a minute.
120
    pub duplicate: bool,
121
}
122
123
/// An unread mention or direct message (spec §14.5).
124
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
125
pub struct InboxItem {
126
    pub id: String,
127
    pub kind: String,
128
    pub at_ms: i64,
129
    pub from: String,
130
    /// `claude-oxygen · Andy McCall's agent`, or the human's name.
131
    #[serde(default)]
132
    pub from_display: String,
133
    pub channel: String,
134
    pub summary: String,
135
}
136
137
/// `POST /api/v1/inbox/ack`.
138
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
139
pub struct Ack {
140
    pub ids: Vec<String>,
141
    #[serde(default)]
142
    pub reaction: Option<String>,
143
}
144
145
/// A search hit (spec §13.1).
146
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
147
pub struct Hit {
148
    pub id: String,
149
    pub channel: String,
150
    pub author: String,
151
    pub at_ms: i64,
152
    pub snippet: String,
153
    pub thread: Option<String>,
154
    #[serde(default)]
155
    pub author_display: String,
156
}
157
158
/// An event as the log and the stream show it: never its payload's data on
159
/// the stream, which says only what changed (spec §17.3).
160
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
161
pub struct EventSummary {
162
    pub seq: u64,
163
    pub id: String,
164
    pub at_ms: u64,
165
    pub kind: String,
166
    pub actor: String,
167
    pub subject: Option<String>,
168
    /// system, token, signature, passkey or session.
169
    pub method: String,
170
    pub assurance: u8,
171
    pub hash: String,
172
}
173
174
/// A page of the log, oldest first.
175
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
176
pub struct Events {
177
    pub items: Vec<EventSummary>,
178
    pub next: Option<u64>,
179
}
180
181
/// `GET /api/v1/brief`: who the caller is and what is waiting.
182
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
183
pub struct Brief {
184
    pub you: String,
185
    pub actor: String,
186
    pub project: Option<String>,
187
    pub rules_version: String,
188
    pub unread: i64,
189
    pub inbox: Vec<InboxItem>,
190
    pub agents: Vec<Agent>,
191
    pub head: u64,
192
}
193
194
/// Every type, with its schema, for `make ts`.
195
pub fn schemas() -> Vec<(&'static str, schemars::Schema)> {
196
    vec![
197
        ("Problem", schemars::schema_for!(Problem)),
198
        ("Version", schemars::schema_for!(Version)),
199
        ("Agent", schemars::schema_for!(Agent)),
200
        ("Project", schemars::schema_for!(Project)),
201
        ("Channel", schemars::schema_for!(Channel)),
202
        ("Message", schemars::schema_for!(Message)),
203
        ("Messages", schemars::schema_for!(Messages)),
204
        ("NewMessage", schemars::schema_for!(NewMessage)),
205
        ("Posted", schemars::schema_for!(Posted)),
206
        ("InboxItem", schemars::schema_for!(InboxItem)),
207
        ("Ack", schemars::schema_for!(Ack)),
208
        ("Hit", schemars::schema_for!(Hit)),
209
        ("Briefs", schemars::schema_for!(Briefs)),
210
        ("ProjectBrief", schemars::schema_for!(ProjectBrief)),
211
        ("SetBrief", schemars::schema_for!(SetBrief)),
212
        ("EventSummary", schemars::schema_for!(EventSummary)),
213
        ("Events", schemars::schema_for!(Events)),
214
        ("Brief", schemars::schema_for!(Brief)),
215
    ]
216
}
217
218
/// `GET /api/v1/briefs`: the overall brief and each project's (spec §12.1).
219
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
220
pub struct Briefs {
221
    pub overall: Option<String>,
222
    pub projects: Vec<ProjectBrief>,
223
}
224
225
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
226
pub struct ProjectBrief {
227
    pub project: String,
228
    pub text: String,
229
}
230
231
/// `PUT /api/v1/briefs`: set the overall brief, or a project's with
232
/// `project`; an empty text clears it.
233
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
234
pub struct SetBrief {
235
    #[serde(default)]
236
    pub project: Option<String>,
237
    pub text: String,
238
}