Skip to main content

ai_crew_sync/
model.rs

1//! Wire types returned by the MCP tools.
2//!
3//! Timestamps are RFC 3339 strings rather than typed datetimes: the consumer is
4//! a language model, and a plain string is both unambiguous and free of extra
5//! schema dependencies.
6
7use schemars::JsonSchema;
8use serde::Serialize;
9
10/// `serde_json::Value` fields would produce a boolean `true` schema, which
11/// some MCP clients' validators reject; an empty object schema means the same
12/// ("anything") and passes everywhere.
13pub fn any_json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
14    schemars::json_schema!({})
15}
16
17pub fn ts(dt: chrono::DateTime<chrono::Utc>) -> String {
18    dt.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
19}
20
21pub fn ts_opt(dt: Option<chrono::DateTime<chrono::Utc>>) -> Option<String> {
22    dt.map(ts)
23}
24
25#[derive(Debug, Serialize, JsonSchema)]
26pub struct WhoAmI {
27    /// Your agent handle. Other agents address you by this name.
28    pub agent: String,
29    pub agent_id: String,
30    pub team: String,
31    pub team_id: String,
32    /// Which of your concurrent working contexts this connection is, taken
33    /// from the `X-Crew-Session` header — usually the repository you are in.
34    /// `null` means the shared session: you sent no header, and your presence,
35    /// task claims and locks are not separated from your other sessions.
36    pub session: Option<String>,
37    /// Discovery labels this session last published with `heartbeat`
38    /// (`project`, `role`). Absent until set, so a client that never labels
39    /// its windows sees exactly the response it saw before.
40    #[serde(skip_serializing_if = "Option::is_none", default)]
41    pub project: Option<String>,
42    #[serde(skip_serializing_if = "Option::is_none", default)]
43    pub role: Option<String>,
44    /// Set when this connection authenticated with a session credential:
45    /// the session label above is then *proven*, not merely asserted in a
46    /// header. `null` means a plain agent token with a header label, which
47    /// is still how every existing client connects.
48    #[serde(skip_serializing_if = "Option::is_none", default)]
49    pub session_identity: Option<SessionIdentity>,
50    /// Channel this session posts to when `post_message` is called with
51    /// neither `channel` nor `to` — the one named after your session, if the
52    /// team has one. `null` means there is none, so you must always say where
53    /// a message goes.
54    pub default_channel: Option<String>,
55    /// Number of unread direct messages waiting for you.
56    pub unread_direct_messages: i64,
57    /// Tasks currently claimed by you and not yet completed.
58    pub open_claimed_tasks: i64,
59}
60
61// ------------------------------------------------------------------ agents --
62
63#[derive(Debug, Serialize, JsonSchema)]
64pub struct AgentInfo {
65    pub name: String,
66    pub display_name: Option<String>,
67    /// Which working context the fields below describe — usually a repository
68    /// name. Absent for the shared session, used by clients that send no
69    /// `X-Crew-Session` header, so a roster of teammates who use no sessions
70    /// serialises exactly as it did before sessions existed.
71    ///
72    /// Which row the summary describes, in order: a **live** session before a
73    /// dead one, a **named** session before the shared one, then the most
74    /// recently updated. Live comes first deliberately — a named session that
75    /// died days ago should not outrank a shared row that is active now — so
76    /// the shared row can win while every named session is offline. Read
77    /// `sessions` when you need all of them; this is one of several.
78    #[serde(skip_serializing_if = "Option::is_none", default)]
79    pub session: Option<String>,
80    /// One of `active`, `idle`, `offline`. `offline` means the presence lease
81    /// expired, i.e. the agent has not sent a heartbeat recently.
82    pub status: String,
83    pub repo: Option<String>,
84    pub branch: Option<String>,
85    /// Free-text description of what this agent is currently doing.
86    pub activity: Option<String>,
87    /// Discovery labels the session set about itself (see `list_sessions`).
88    /// Absent when never set.
89    #[serde(skip_serializing_if = "Option::is_none", default)]
90    pub project: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none", default)]
92    pub role: Option<String>,
93    pub last_seen: Option<String>,
94    /// True when *any* of this agent's sessions has a live presence lease.
95    pub online: bool,
96    /// Every working context this agent has open, most recently active first.
97    /// Absent when there is only one — the fields above already describe it.
98    /// A teammate with several entries here is working in several repositories
99    /// at once, and each one claims tasks and holds locks independently.
100    #[serde(skip_serializing_if = "Vec::is_empty", default)]
101    pub sessions: Vec<AgentSession>,
102}
103
104/// A session credential as the caller receives it. `session_token` is the
105/// secret and appears exactly once, on registration.
106#[derive(Debug, Serialize, JsonSchema)]
107pub struct SessionCredential {
108    /// The credential. Store it in a private file (0600) and send it as the
109    /// bearer token from now on; it is not shown again. Absent on a renewal,
110    /// which extends the credential you already hold.
111    #[serde(skip_serializing_if = "Option::is_none", default)]
112    pub session_token: Option<String>,
113    pub session_id: String,
114    /// The label this credential authenticates as.
115    pub session: String,
116    /// What a teammate puts in `to` to reach exactly this window.
117    pub address: String,
118    /// Connection epoch. Send it as `X-Crew-Epoch` to be fenced off cleanly
119    /// if another process resumes this window after you.
120    pub epoch: i64,
121    pub expires_at: String,
122    pub expires_in_seconds: i64,
123}
124
125/// What a session credential proves, reported by `whoami`.
126#[derive(Debug, Serialize, JsonSchema)]
127pub struct SessionIdentity {
128    pub session_id: String,
129    pub epoch: i64,
130    pub registered_at: String,
131    pub expires_at: String,
132    pub expires_in_seconds: i64,
133}
134
135/// One working context of an agent: what that session is doing right now.
136#[derive(Debug, Serialize, JsonSchema)]
137pub struct AgentSession {
138    /// Absent for the shared session.
139    #[serde(skip_serializing_if = "Option::is_none", default)]
140    pub session: Option<String>,
141    pub status: String,
142    pub repo: Option<String>,
143    pub branch: Option<String>,
144    pub activity: Option<String>,
145    /// Discovery labels this session set about itself. Absent when never set.
146    #[serde(skip_serializing_if = "Option::is_none", default)]
147    pub project: Option<String>,
148    #[serde(skip_serializing_if = "Option::is_none", default)]
149    pub role: Option<String>,
150    pub last_seen: Option<String>,
151    pub online: bool,
152}
153
154/// One session as `list_sessions` reports it: addressable, with its
155/// discovery labels. Two sessions may share every label and still be two
156/// entries, because the address differs.
157#[derive(Debug, Serialize, JsonSchema)]
158pub struct SessionEntry {
159    pub agent: String,
160    /// Absent for the shared session.
161    #[serde(skip_serializing_if = "Option::is_none", default)]
162    pub session: Option<String>,
163    /// What to put in `to` (or `ask_agent`'s `to`) to reach this session.
164    /// `agent/session` for a named one; the bare `agent` for the shared
165    /// session — and see `exact` before treating that as private.
166    pub address: String,
167    /// True when `address` reaches **this window and no other**. False for
168    /// the shared session, whose address is the bare agent name: that is a
169    /// broadcast to every window of that agent, named ones included, so it
170    /// is the wrong place to send a private instruction. There is no address
171    /// that reaches the shared session alone.
172    pub exact: bool,
173    pub project: Option<String>,
174    pub role: Option<String>,
175    pub repo: Option<String>,
176    pub branch: Option<String>,
177    pub activity: Option<String>,
178    /// One of `active`, `idle`, `busy`, `blocked`, `offline`.
179    pub status: String,
180    pub online: bool,
181    pub last_seen: Option<String>,
182}
183
184#[derive(Debug, Serialize, JsonSchema)]
185pub struct SessionList {
186    pub sessions: Vec<SessionEntry>,
187    /// Sessions returned. When it equals the limit there may be more: narrow
188    /// with `project` or `role`.
189    pub count: usize,
190    pub limit: i64,
191}
192
193#[derive(Debug, Serialize, JsonSchema)]
194pub struct AgentList {
195    pub agents: Vec<AgentInfo>,
196    /// Agents with at least one live session — people, not sessions.
197    pub online_count: usize,
198}
199
200// -------------------------------------------------------------- messaging --
201
202#[derive(Debug, Serialize, JsonSchema)]
203pub struct ChannelInfo {
204    pub name: String,
205    pub topic: Option<String>,
206    pub message_count: i64,
207    pub created_at: String,
208}
209
210#[derive(Debug, Serialize, JsonSchema)]
211pub struct ChannelList {
212    pub channels: Vec<ChannelInfo>,
213}
214
215#[derive(Debug, Serialize, JsonSchema)]
216pub struct MessageInfo {
217    pub id: i64,
218    pub from: String,
219    /// Which of the sender's working contexts wrote this; `null` is their
220    /// shared session. Reply to `from/from_session` to reach the window that
221    /// is waiting, rather than whichever one notices first.
222    pub from_session: Option<String>,
223    /// True when this was posted as an announcement: something the sender
224    /// judged worth interrupting the whole team for, so it reaches every
225    /// session regardless of which channel they are focused on.
226    pub announce: bool,
227    /// Channel name for channel messages; `null` for direct messages.
228    pub channel: Option<String>,
229    /// Recipient handle for direct messages; `null` for channel messages.
230    pub to: Option<String>,
231    /// Set when this direct message was addressed to one working context of
232    /// the recipient rather than to the person. `null` means every session of
233    /// theirs sees it.
234    pub to_session: Option<String>,
235    pub body: String,
236    pub reply_to: Option<i64>,
237    #[schemars(schema_with = "any_json_schema")]
238    pub metadata: serde_json::Value,
239    /// Files attached to this message; fetch content with get_attachment.
240    pub attachments: Vec<AttachmentMeta>,
241    pub created_at: String,
242}
243
244#[derive(Debug, Serialize, serde::Deserialize, JsonSchema)]
245pub struct AttachmentMeta {
246    /// Pass this id to get_attachment to download the content.
247    pub id: i64,
248    pub filename: String,
249    pub content_type: String,
250    pub size_bytes: i64,
251}
252
253#[derive(Debug, Serialize, JsonSchema)]
254pub struct AttachmentContent {
255    pub id: i64,
256    pub filename: String,
257    pub content_type: String,
258    pub size_bytes: i64,
259    pub uploaded_by: String,
260    pub created_at: String,
261    /// The file content, base64-encoded.
262    pub data_base64: String,
263}
264
265#[derive(Debug, Serialize, JsonSchema)]
266pub struct PostMessageResult {
267    pub message: MessageInfo,
268    /// Handles that can now see this message.
269    pub delivered_to: Vec<String>,
270}
271
272#[derive(Debug, Serialize, JsonSchema)]
273pub struct MessageList {
274    pub messages: Vec<MessageInfo>,
275    /// The scope that was actually read, after normalisation.
276    pub scope: String,
277    /// Read cursor position after this call. Messages at or below this id will
278    /// not be returned again when `only_new` is true.
279    pub cursor: i64,
280    /// True when the result hit `limit` and older/newer messages remain.
281    pub truncated: bool,
282}
283
284// ------------------------------------------------------------------ tasks --
285
286#[derive(Debug, Serialize, JsonSchema)]
287pub struct TaskInfo {
288    pub key: String,
289    pub title: String,
290    pub description: Option<String>,
291    /// One of `open`, `claimed`, `done`, `cancelled`. A claim whose lease
292    /// lapsed reads as `open`: anyone may take it, the former holder
293    /// included (see `lapsed_holder`).
294    pub status: String,
295    /// Keys of tasks this one depends on.
296    pub depends_on: Vec<String>,
297    /// True while any dependency is not yet done/cancelled. Blocked tasks
298    /// cannot be claimed.
299    pub blocked: bool,
300    pub claimed_by: Option<String>,
301    /// Which of `claimed_by`'s working contexts holds the claim; `null` is
302    /// their shared session. A claim belongs to a session, not to a person —
303    /// your own other session cannot renew, release or steal this one.
304    pub claimed_session: Option<String>,
305    pub claimed_at: Option<String>,
306    /// When the current claim expires. After this instant another agent may
307    /// steal the task, so renew the lease if you are still working on it.
308    pub lease_expires_at: Option<String>,
309    /// Seconds left on the claim, so you can decide whether waiting is
310    /// reasonable without doing the arithmetic.
311    pub lease_seconds_remaining: Option<i64>,
312    /// True when the last claim lapsed and nobody has claimed the task
313    /// since: it is `open`, and `lapsed_holder` says who let it go.
314    pub lease_expired: bool,
315    /// Who held the claim that lapsed, while the task stays unclaimed. Not a
316    /// holder: nobody has to be asked before claiming it.
317    pub lapsed_holder: Option<String>,
318    pub result: Option<String>,
319    #[schemars(schema_with = "any_json_schema")]
320    pub metadata: serde_json::Value,
321    /// Files attached to this task; fetch content with get_attachment.
322    pub attachments: Vec<AttachmentMeta>,
323    pub created_by: Option<String>,
324    pub created_at: String,
325    pub updated_at: String,
326}
327
328#[derive(Debug, Serialize, JsonSchema)]
329pub struct TaskList {
330    pub tasks: Vec<TaskInfo>,
331    pub open: i64,
332    pub claimed: i64,
333}
334
335#[derive(Debug, Serialize, JsonSchema)]
336pub struct ClaimResult {
337    pub claimed: bool,
338    pub task: Option<TaskInfo>,
339    /// Present when `claimed` is false: why the claim did not succeed.
340    pub reason: Option<String>,
341}
342
343#[derive(Debug, Serialize, JsonSchema)]
344pub struct TaskEventInfo {
345    pub event: String,
346    pub agent: Option<String>,
347    pub detail: Option<String>,
348    pub created_at: String,
349}
350
351#[derive(Debug, Serialize, JsonSchema)]
352pub struct TaskDetail {
353    pub task: TaskInfo,
354    pub history: Vec<TaskEventInfo>,
355}
356
357// ------------------------------------------------------------------ notes --
358
359#[derive(Debug, Serialize, JsonSchema)]
360pub struct NoteInfo {
361    pub scope: String,
362    pub key: String,
363    pub value: String,
364    pub tags: Vec<String>,
365    pub updated_by: Option<String>,
366    pub updated_at: String,
367}
368
369#[derive(Debug, Serialize, JsonSchema)]
370pub struct NoteList {
371    pub notes: Vec<NoteInfo>,
372}
373
374#[derive(Debug, Serialize, JsonSchema)]
375pub struct NoteRef {
376    pub scope: String,
377    pub key: String,
378    pub found: bool,
379    pub note: Option<NoteInfo>,
380}
381
382#[derive(Debug, Serialize, JsonSchema)]
383pub struct Ack {
384    pub ok: bool,
385    pub detail: String,
386}
387
388// ------------------------------------------------------------------ locks --
389
390#[derive(Debug, Serialize, JsonSchema)]
391pub struct LockInfo {
392    pub name: String,
393    pub holder: String,
394    /// Which of the holder's working contexts took it; `null` is their shared
395    /// session. A lock belongs to a session — your own other session cannot
396    /// release it or take it over while it is live.
397    pub holder_session: Option<String>,
398    pub purpose: Option<String>,
399    pub acquired_at: String,
400    /// When the lock lapses on its own if not renewed.
401    pub expires_at: String,
402}
403
404#[derive(Debug, Serialize, JsonSchema)]
405pub struct LockList {
406    pub locks: Vec<LockInfo>,
407}
408
409#[derive(Debug, Serialize, JsonSchema)]
410pub struct LockResult {
411    pub acquired: bool,
412    pub lock: Option<LockInfo>,
413    /// Present when `acquired` is false: who holds it and until when.
414    pub reason: Option<String>,
415}
416
417// ----------------------------------------------------------------- events --
418
419#[derive(Debug, Serialize, JsonSchema)]
420pub struct WaitEvent {
421    /// One of `message`, `task`, `lock`, `note`.
422    pub kind: String,
423    /// Human-readable one-liner of what happened.
424    pub summary: String,
425}
426
427#[derive(Debug, Serialize, JsonSchema)]
428pub struct WaitResult {
429    /// True when something happened; false when the timeout elapsed quietly.
430    pub woke: bool,
431    pub timed_out: bool,
432    pub events: Vec<WaitEvent>,
433    /// Unread direct messages after the wait — if > 0, call read_messages.
434    pub unread_direct_messages: i64,
435    /// What to do next, e.g. which tool to call to fetch the details.
436    pub suggestion: String,
437}
438
439#[derive(Debug, Serialize, JsonSchema)]
440pub struct AskResult {
441    /// True when the teammate answered before the timeout.
442    pub answered: bool,
443    /// The agent the question was addressed to.
444    pub to: String,
445    /// Id of the question message. On timeout, pass it back as
446    /// `resume_message_id` to keep waiting without re-sending the question.
447    pub question_message_id: i64,
448    /// The answer: their reply to the question, or failing that their first
449    /// direct message to you after it.
450    pub answer: Option<MessageInfo>,
451    /// What to do next.
452    pub suggestion: String,
453}
454
455// ----------------------------------------------------------------- digest --
456
457#[derive(Debug, Serialize, JsonSchema)]
458pub struct DigestMessage {
459    pub from: String,
460    pub body: String,
461    pub at: String,
462}
463
464#[derive(Debug, Serialize, JsonSchema)]
465pub struct DigestChannel {
466    pub name: String,
467    pub message_count: i64,
468    pub last_messages: Vec<DigestMessage>,
469}
470
471#[derive(Debug, Serialize, JsonSchema)]
472pub struct DigestTask {
473    pub key: String,
474    pub title: String,
475    pub status: String,
476    pub claimed_by: Option<String>,
477    pub result: Option<String>,
478    pub updated_at: String,
479}
480
481#[derive(Debug, Serialize, JsonSchema)]
482pub struct DigestNote {
483    pub scope: String,
484    pub key: String,
485    pub updated_by: Option<String>,
486    pub updated_at: String,
487}
488
489#[derive(Debug, Serialize, JsonSchema)]
490pub struct DigestAgent {
491    pub name: String,
492    pub activity: Option<String>,
493    pub last_seen: Option<String>,
494    pub online: bool,
495}
496
497#[derive(Debug, Serialize, JsonSchema)]
498pub struct DigestResult {
499    /// Window covered, in hours.
500    pub hours: i64,
501    pub channels: Vec<DigestChannel>,
502    /// Tasks whose state changed inside the window, newest first.
503    pub tasks_moved: Vec<DigestTask>,
504    pub open_tasks: i64,
505    pub claimed_tasks: i64,
506    pub notes_updated: Vec<DigestNote>,
507    pub agents_seen: Vec<DigestAgent>,
508    pub active_locks: Vec<LockInfo>,
509}
510
511// ----------------------------------------------------------- conversations --
512
513/// A project: the unit a conversation can be visible to. Access is an
514/// explicit grant, never inferred from a directory or a role label.
515#[derive(Debug, Serialize, JsonSchema)]
516pub struct ProjectInfo {
517    pub id: String,
518    pub name: String,
519    /// Agents with an explicit grant. Only visible to someone who has one.
520    pub members: Vec<String>,
521    pub archived: bool,
522    pub created_at: String,
523}
524
525#[derive(Debug, Serialize, JsonSchema)]
526pub struct ProjectList {
527    pub projects: Vec<ProjectInfo>,
528}
529
530/// One conversation as a caller sees it.
531#[derive(Debug, Serialize, JsonSchema)]
532pub struct ConversationInfo {
533    pub id: String,
534    pub title: String,
535    /// `project` (everyone with access to the project can read it) or
536    /// `private` (only its members). Fixed at creation.
537    pub visibility: String,
538    /// Absent for a private conversation.
539    #[serde(skip_serializing_if = "Option::is_none", default)]
540    pub project: Option<String>,
541    pub created_by: String,
542    pub created_at: String,
543    pub archived: bool,
544    /// Highest logical sequence in the thread. Messages are paged by this.
545    pub last_seq: i64,
546    /// Your own membership, when you have one.
547    #[serde(skip_serializing_if = "Option::is_none", default)]
548    pub membership: Option<MembershipInfo>,
549    /// Everyone in the thread. Only returned to a member.
550    #[serde(skip_serializing_if = "Vec::is_empty", default)]
551    pub members: Vec<MembershipInfo>,
552}
553
554#[derive(Clone, Debug, Serialize, JsonSchema)]
555pub struct MembershipInfo {
556    pub membership_id: String,
557    pub agent: String,
558    /// Absent for the shared session.
559    #[serde(skip_serializing_if = "Option::is_none", default)]
560    pub session: Option<String>,
561    /// `agent/session`, or the bare agent for the shared session.
562    pub address: String,
563    /// `owner`, `moderator`, `participant` or `observer`.
564    pub role: String,
565    /// `invited`, `active`, `left` or `removed`.
566    pub state: String,
567    /// Lowest sequence this member may read; `null` means from the start.
568    pub history_from_seq: Option<i64>,
569    pub invited_at: String,
570    pub accepted_at: Option<String>,
571}
572
573#[derive(Debug, Serialize, JsonSchema)]
574pub struct ConversationList {
575    pub conversations: Vec<ConversationInfo>,
576}
577
578/// What a send returns. `stored` and `publication` are facts about
579/// persistence at the moment of this reply, never about anyone having read
580/// anything.
581#[derive(Debug, Serialize, JsonSchema)]
582pub struct SentMessage {
583    pub message_id: String,
584    pub conversation_id: String,
585    pub seq: i64,
586    /// True when the backend that holds this body had confirmed it when this
587    /// reply was written. On a thread stored in Postgres that is the send's
588    /// own commit, so it is always true. On a thread published through an
589    /// outbox (a team routed to JetStream) the send is accepted and recorded
590    /// first and the backend's answer comes later, so a fresh send says
591    /// `false` with `publication: "pending_publication"`. That state is not
592    /// final: it settles as `stored`, or as `failed` if the backend refuses
593    /// the body for good. While it is pending, do NOT send the message again.
594    /// Watch it settle with
595    /// `get_conversation_message` or `get_message_receipts` (`stored_at`),
596    /// or repeat the call with the same `request_id`, which returns the same
597    /// message with its current state.
598    pub stored: bool,
599    /// Where the body stands with its backend right now, in the same words
600    /// a read of the message uses: `stored`, `pending_publication`
601    /// (accepted, not yet confirmed) or `failed` (it will not be published;
602    /// the message keeps its place and the gap stays visible). `stored` is
603    /// true exactly when this is `"stored"`.
604    pub publication: String,
605    /// Who the message was addressed to, snapshotted now. A later join never
606    /// enters this list.
607    pub recipients: Vec<String>,
608    pub created_at: String,
609}
610
611/// One message of a thread.
612#[derive(Debug, Serialize, JsonSchema)]
613pub struct ConversationMessage {
614    pub message_id: String,
615    pub seq: i64,
616    pub from: String,
617    /// `agent/session` of the sender, for an exact reply.
618    pub from_address: String,
619    /// The text the sender wrote, or an EMPTY STRING when `unavailable` is
620    /// set. Check `unavailable` before quoting or summarising: an empty
621    /// body with a reason there is a body this bus cannot give you — not
622    /// yet, or not any more — never an empty message from your teammate.
623    pub body: String,
624    pub reply_to: Option<String>,
625    pub metadata: serde_json::Value,
626    pub created_at: String,
627    /// Your own observations on this message, when you are a recipient.
628    #[serde(skip_serializing_if = "Option::is_none", default)]
629    pub my_receipt: Option<ReceiptInfo>,
630    /// Where this body stands with the backend that holds it: `stored`
631    /// normally, and `pending_publication`, `failed` or `tombstoned` when
632    /// the body is not here. A thread on the default Postgres backend is
633    /// always `stored`.
634    pub publication: String,
635    /// Always present. `null` when `body` is the real text; otherwise why
636    /// the body is not here, and `body` is an empty placeholder. The reason
637    /// says which case it is: a backend that cannot be reached right now
638    /// (try again later), a body that was never stored, or one the backend
639    /// no longer holds (those two will not come back). The message keeps
640    /// its place in the sequence, its sender and its receipts either way: a
641    /// gap you can see and read about is not the same as a gap.
642    #[serde(default)]
643    pub unavailable: Option<String>,
644}
645
646#[derive(Debug, Serialize, JsonSchema)]
647pub struct ConversationRead {
648    pub conversation_id: String,
649    pub messages: Vec<ConversationMessage>,
650    /// Pass as `after_seq` to continue. Absent when the thread is exhausted.
651    #[serde(skip_serializing_if = "Option::is_none", default)]
652    pub next_after_seq: Option<i64>,
653    /// Lowest sequence you may read in this thread.
654    pub history_from_seq: Option<i64>,
655}
656
657/// One reference to a message, as a recipient's inbox hands it over. It
658/// carries no body: the body is read separately, with a current access
659/// check at that moment.
660#[derive(Clone, Debug, Serialize, JsonSchema)]
661pub struct InboxReference {
662    /// Pass this to `confirm_inbox_delivery` once you hold the reference
663    /// durably. Until you do, nothing has been marked delivered.
664    pub delivery_id: String,
665    pub message_id: String,
666    pub conversation_id: String,
667    pub seq: i64,
668    pub from: String,
669    /// `agent/session` of the sender, for an exact reply.
670    pub from_address: String,
671    pub created_at: String,
672    /// True when this reference has been offered before: an earlier
673    /// confirmation was lost, or the process holding it went away. Handling
674    /// it twice must change nothing.
675    pub redelivered: bool,
676    /// `broker` when it came from the durable inbox, `bus` when it was
677    /// rebuilt from the bus's own records after an expiry, a deleted
678    /// consumer, or a team that is not routed to a broker at all.
679    pub source: String,
680    /// `message` — this message was addressed to you — or `receipt`: a
681    /// message *you sent* has a receipt worth reading again. A receipt
682    /// reference never means someone read anything; `get_message_receipts`
683    /// says what actually happened.
684    pub kind: String,
685}
686
687#[derive(Debug, Serialize, JsonSchema)]
688pub struct InboxBatch {
689    pub references: Vec<InboxReference>,
690    /// How many of them the broker supplied. The rest were rebuilt from the
691    /// bus's own records, which are the authority.
692    pub from_broker: i64,
693    /// True when the batch filled: call again.
694    pub more: bool,
695    /// Present when something is worth saying about where these references
696    /// came from: an unreachable broker, a missing consumer, a team whose
697    /// conversations live on Postgres.
698    ///
699    /// Written for you, not for an operator. It never carries backend
700    /// detail — no stream names, no error codes, no credentials — and never
701    /// asks you to run something only an operator can; that detail is in
702    /// the server log. A note is not an error and not a reason to retry the
703    /// call that produced it: the page beside it is complete either way.
704    /// It never decides pagination: when it mentions paging it only repeats
705    /// that `more` is the authority, and `more` alone says whether to call
706    /// again.
707    #[serde(skip_serializing_if = "Option::is_none", default)]
708    pub note: Option<String>,
709}
710
711/// What one window's inbox holds. The two sides are reported separately on
712/// purpose: they answer different questions, and averaging them would hide
713/// exactly the case worth seeing.
714#[derive(Debug, Serialize, JsonSchema)]
715pub struct InboxState {
716    pub address: String,
717    /// Messages addressed to this window that it has never confirmed
718    /// holding. The authoritative number.
719    pub undelivered: i64,
720    /// References handed to a process that has not confirmed them. A
721    /// non-zero number here after a crash is expected: they are offered
722    /// again.
723    pub handed_out_unconfirmed: i64,
724    /// What the broker still holds for this window, when there is one.
725    #[serde(skip_serializing_if = "Option::is_none", default)]
726    pub broker_pending: Option<i64>,
727    #[serde(skip_serializing_if = "Option::is_none", default)]
728    pub broker_awaiting_ack: Option<i64>,
729    /// False means the durable consumer is gone (expired, or removed).
730    /// That is not an empty inbox: the bus's own records still have it.
731    #[serde(skip_serializing_if = "Option::is_none", default)]
732    pub broker_consumer_present: Option<bool>,
733}
734
735/// Five independent observations. An absent timestamp means *not observed*,
736/// never "assumed": a cursor moving is not a person reading, and a host that
737/// cannot confirm injection leaves `presented_at` null.
738#[derive(Clone, Debug, Serialize, JsonSchema)]
739pub struct ReceiptInfo {
740    pub agent: String,
741    #[serde(skip_serializing_if = "Option::is_none", default)]
742    pub session: Option<String>,
743    pub address: String,
744    pub stored_at: Option<String>,
745    pub delivered_at: Option<String>,
746    /// Null when the host cannot confirm the message reached the model. That
747    /// is unknown, not "no".
748    pub presented_at: Option<String>,
749    pub acknowledged_at: Option<String>,
750    /// The recipient said it acted on this. It does not complete a task or
751    /// merge anything by itself.
752    pub resolved_at: Option<String>,
753    pub note: Option<String>,
754}
755
756#[derive(Debug, Serialize, JsonSchema)]
757pub struct MessageReceipts {
758    pub message_id: String,
759    pub seq: i64,
760    /// One entry per recipient at acceptance time.
761    pub receipts: Vec<ReceiptInfo>,
762    pub acknowledged: usize,
763    pub resolved: usize,
764    pub total: usize,
765}
766
767/// What `wait_for_conversation_updates` reports.
768#[derive(Debug, Serialize, JsonSchema)]
769pub struct ConversationUpdates {
770    pub conversations: Vec<ConversationActivity>,
771    pub waited_seconds: i64,
772}
773
774#[derive(Debug, Serialize, JsonSchema)]
775pub struct ConversationActivity {
776    pub conversation_id: String,
777    pub title: String,
778    /// Highest sequence currently stored in the thread. It is not a read
779    /// cursor: it does not move with what you have read or acknowledged.
780    pub last_seq: i64,
781    /// Messages addressed to you that you have not acknowledged.
782    pub unacknowledged: i64,
783}
784
785/// Result of a membership transfer proposal or acceptance.
786#[derive(Debug, Serialize, JsonSchema)]
787pub struct TransferResult {
788    pub conversation_id: String,
789    /// The membership that will be superseded once the target accepts.
790    pub from_address: String,
791    pub to_address: String,
792    pub state: String,
793}