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}