Skip to main content

objectiveai_sdk/client_objectiveai_mcp/server_response/
payload.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4/// Tagged union of every reply the CLI can send back to the API in
5/// answer to a [`super::super::server_request::Payload`]. Variant
6/// names pair 1:1 with the request side; the typed `result` /
7/// `error` shape per JSON-RPC method is captured in
8/// [`JsonRpcResult`].
9///
10/// MCP-routed variants echo `mcp_kind` on the variant itself
11/// (lets the API sanity-check routing). Non-MCP variants
12/// (`ReadMessageQueue` / `ClearMessageQueue`) don't carry
13/// `mcp_kind` — they never had one to echo. Use
14/// [`Payload::mcp_kind`] to retrieve it generically.
15#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
16#[schemars(rename = "client_objectiveai_mcp.server_response.Payload")]
17#[serde(tag = "type", rename_all = "snake_case")]
18pub enum Payload {
19    /// Reply to
20    /// [`super::super::server_request::Payload::Initialize`]. On
21    /// success carries the upstream MCP session id the API stamps
22    /// onto its outbound `Mcp-Session-Id` response header so the
23    /// proxy adopts it. On failure (dial or aggregate-build error)
24    /// carries a JSON-RPC error envelope the API translates into its
25    /// own outbound error.
26    #[schemars(title = "Initialize")]
27    Initialize {
28        mcp_kind: super::super::McpKind,
29        #[serde(flatten)]
30        result: JsonRpcResult<InitializeReply>,
31    },
32
33    /// Reply to [`super::super::server_request::Payload::ToolsList`].
34    #[schemars(title = "ToolsList")]
35    ToolsList {
36        mcp_kind: super::super::McpKind,
37        #[serde(flatten)]
38        result: JsonRpcResult<crate::mcp::tool::ListToolsResult>,
39    },
40
41    /// Reply to [`super::super::server_request::Payload::ToolsCall`].
42    #[schemars(title = "ToolsCall")]
43    ToolsCall {
44        mcp_kind: super::super::McpKind,
45        #[serde(flatten)]
46        result: JsonRpcResult<crate::mcp::tool::CallToolResult>,
47    },
48
49    /// Reply to
50    /// [`super::super::server_request::Payload::ResourcesList`].
51    #[schemars(title = "ResourcesList")]
52    ResourcesList {
53        mcp_kind: super::super::McpKind,
54        #[serde(flatten)]
55        result: JsonRpcResult<crate::mcp::resource::ListResourcesResult>,
56    },
57
58    /// Reply to
59    /// [`super::super::server_request::Payload::ResourcesRead`].
60    #[schemars(title = "ResourcesRead")]
61    ResourcesRead {
62        mcp_kind: super::super::McpKind,
63        #[serde(flatten)]
64        result: JsonRpcResult<crate::mcp::resource::ReadResourceResult>,
65    },
66
67    /// Acknowledges
68    /// [`super::super::server_request::Payload::SessionTerminate`].
69    /// On success carries the unit value (no body); on failure
70    /// carries the upstream-delete error so the proxy sees a
71    /// non-2xx and can retry.
72    #[schemars(title = "SessionTerminate")]
73    SessionTerminate {
74        mcp_kind: super::super::McpKind,
75        #[serde(flatten)]
76        result: JsonRpcResult<()>,
77    },
78
79    /// Reply to
80    /// [`super::super::server_request::Payload::ReadMessageQueue`].
81    /// On success carries every matching queue row's id + body in
82    /// oldest-first order; on failure surfaces the local storage
83    /// error so the API can decide whether to retry. Non-MCP — no
84    /// `mcp_kind` to echo.
85    #[schemars(title = "ReadMessageQueue")]
86    ReadMessageQueue(JsonRpcResult<ReadMessageQueueResult>),
87
88    /// Reply to
89    /// [`super::super::server_request::Payload::Retrieve`]. Carries the
90    /// resolved definition (or `None` if not found) on success, or the
91    /// client's local storage error. Non-MCP — no `mcp_kind`.
92    #[schemars(title = "Retrieve")]
93    Retrieve(JsonRpcResult<super::super::retrieve::Response>),
94
95    /// Reply to
96    /// [`super::super::server_request::Payload::Script`]. On success
97    /// carries the script's output messages (assistant/tool only); on
98    /// failure surfaces the execution error. Non-MCP — no `mcp_kind`.
99    #[schemars(title = "Script")]
100    Script(JsonRpcResult<ScriptResult>),
101
102    /// Acknowledges
103    /// [`super::super::server_request::Payload::Drop`]. Infallible — no
104    /// `JsonRpcResult` wrapper; carries `dropped`: whether a bucket for
105    /// the response id was present and removed. Non-MCP — no `mcp_kind`.
106    #[schemars(title = "Drop")]
107    Drop(DropResult),
108
109    /// Reply to
110    /// [`super::super::server_request::Payload::LaboratoryExportBegin`].
111    /// Non-MCP — no `mcp_kind` (same for every transfer variant below).
112    #[schemars(title = "LaboratoryExportBegin")]
113    LaboratoryExportBegin(JsonRpcResult<LaboratoryTransferBeginResult>),
114    /// Reply to
115    /// [`super::super::server_request::Payload::LaboratoryExportRead`].
116    #[schemars(title = "LaboratoryExportRead")]
117    LaboratoryExportRead(JsonRpcResult<LaboratoryExportChunk>),
118    /// Reply to
119    /// [`super::super::server_request::Payload::LaboratoryExportAbort`].
120    #[schemars(title = "LaboratoryExportAbort")]
121    LaboratoryExportAbort(JsonRpcResult<LaboratoryTransferAck>),
122    /// Reply to
123    /// [`super::super::server_request::Payload::LaboratoryImportBegin`].
124    #[schemars(title = "LaboratoryImportBegin")]
125    LaboratoryImportBegin(JsonRpcResult<LaboratoryTransferBeginResult>),
126    /// Reply to
127    /// [`super::super::server_request::Payload::LaboratoryImportWrite`].
128    #[schemars(title = "LaboratoryImportWrite")]
129    LaboratoryImportWrite(JsonRpcResult<LaboratoryTransferAck>),
130    /// Reply to
131    /// [`super::super::server_request::Payload::LaboratoryImportEnd`].
132    /// On success carries the total bytes fed into the laboratory.
133    #[schemars(title = "LaboratoryImportEnd")]
134    LaboratoryImportEnd(JsonRpcResult<LaboratoryImportEndResult>),
135    /// Reply to
136    /// [`super::super::server_request::Payload::LaboratoryImportAbort`].
137    #[schemars(title = "LaboratoryImportAbort")]
138    LaboratoryImportAbort(JsonRpcResult<LaboratoryTransferAck>),
139
140    /// Reply to
141    /// [`super::super::server_request::Payload::LaboratoryTransfer`] —
142    /// the byte total the destination ingested.
143    #[schemars(title = "LaboratoryTransfer")]
144    LaboratoryTransfer(JsonRpcResult<LaboratoryTransferResult>),
145    /// Reply to
146    /// [`super::super::server_request::Payload::LaboratoryLocalTransfer`].
147    #[schemars(title = "LaboratoryLocalTransfer")]
148    LaboratoryLocalTransfer(JsonRpcResult<LaboratoryTransferResult>),
149
150}
151
152impl Payload {
153    /// Which CLI-hosted MCP server produced this reply. `Some` for
154    /// MCP-routed variants (echoes the request's `mcp_kind`); `None`
155    /// for `ReadMessageQueue`.
156    pub fn mcp_kind(&self) -> Option<super::super::McpKind> {
157        match self {
158            Payload::Initialize { mcp_kind, .. }
159            | Payload::ToolsList { mcp_kind, .. }
160            | Payload::ToolsCall { mcp_kind, .. }
161            | Payload::ResourcesList { mcp_kind, .. }
162            | Payload::ResourcesRead { mcp_kind, .. }
163            | Payload::SessionTerminate { mcp_kind, .. } => Some(mcp_kind.clone()),
164            Payload::ReadMessageQueue(_)
165            | Payload::Retrieve(_)
166            | Payload::Script(_)
167            | Payload::Drop(_)
168            | Payload::LaboratoryExportBegin(_)
169            | Payload::LaboratoryExportRead(_)
170            | Payload::LaboratoryExportAbort(_)
171            | Payload::LaboratoryImportBegin(_)
172            | Payload::LaboratoryImportWrite(_)
173            | Payload::LaboratoryImportEnd(_)
174            | Payload::LaboratoryImportAbort(_)
175            | Payload::LaboratoryTransfer(_)
176            | Payload::LaboratoryLocalTransfer(_) => None,
177        }
178    }
179}
180
181/// Successful payload for [`Payload::ReadMessageQueue`].
182///
183/// One [`ReadMessageQueueRow`] per consumed `message_queue` row,
184/// in oldest-first id order. Each row's `content_ids` are the
185/// `message_queue_contents.id`s for that row's slots; the row's
186/// `rich_content` is the CLI's reconstructed payload for that
187/// row alone (no cross-row separator splicing — callers join if
188/// they want a unified User message).
189///
190/// Two consumers:
191/// - **Startup snapshot** (`run_agent_loop`): joins every row's
192///   parts with `"\n\n"` separators, flattens `content_ids`, and
193///   stamps the result onto the first
194///   `AssistantResponseChunk.request_message_ids`.
195/// - **`ApiQueueDelegate`** (`agents logs read subscribe`-style
196///   per-tool-response delivery): keeps rows separate so each
197///   gets converted to its own `Vec<ContentBlock>` and surfaces
198///   row-by-row on tool responses.
199///
200/// The downstream LogWriter resolves each id's kind at write
201/// time (SQL CASE against `message_queue_contents.kind`) to
202/// dispatch the right `logs.message_table` variant — kinds don't
203/// need to ride on the wire.
204#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
205#[schemars(rename = "client_objectiveai_mcp.server_response.ReadMessageQueueResult")]
206pub struct ReadMessageQueueResult {
207    pub rows: Vec<ReadMessageQueueRow>,
208}
209
210/// Result of [`Payload::Drop`]. `dropped` is `true` if a connection
211/// bucket for the response id was present and removed, `false` if no
212/// bucket existed (the drop is idempotent either way).
213#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
214#[schemars(rename = "client_objectiveai_mcp.server_response.DropResult")]
215pub struct DropResult {
216    pub dropped: bool,
217}
218
219/// Successful payload for [`Payload::LaboratoryExportBegin`] /
220/// [`Payload::LaboratoryImportBegin`] — the conduit-minted id the
221/// requester uses for every subsequent op on this transfer half.
222#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
223#[schemars(rename = "client_objectiveai_mcp.server_response.LaboratoryTransferBeginResult")]
224pub struct LaboratoryTransferBeginResult {
225    pub transfer_id: String,
226}
227
228/// Successful payload for [`Payload::LaboratoryExportRead`] — one
229/// pulled chunk. `eof: true` means the export completed and its entry
230/// is gone (this final chunk's `data` may still be non-empty).
231#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
232#[schemars(rename = "client_objectiveai_mcp.server_response.LaboratoryExportChunk")]
233pub struct LaboratoryExportChunk {
234    /// Base64-encoded tar bytes.
235    pub data: String,
236    pub eof: bool,
237}
238
239/// Successful payload for the transfer acknowledgement replies
240/// ([`Payload::LaboratoryExportAbort`], [`Payload::LaboratoryImportWrite`],
241/// [`Payload::LaboratoryImportAbort`]) — no fields, the `Ok` is the ack.
242#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
243#[schemars(rename = "client_objectiveai_mcp.server_response.LaboratoryTransferAck")]
244pub struct LaboratoryTransferAck {}
245
246/// Successful payload for [`Payload::LaboratoryImportEnd`] — the total
247/// archive bytes fed into the destination laboratory.
248#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
249#[schemars(rename = "client_objectiveai_mcp.server_response.LaboratoryImportEndResult")]
250pub struct LaboratoryImportEndResult {
251    pub bytes: u64,
252}
253
254/// Successful payload for [`Payload::LaboratoryTransfer`] /
255/// [`Payload::LaboratoryLocalTransfer`]: the total bytes the
256/// destination laboratory ingested.
257#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
258#[schemars(rename = "client_objectiveai_mcp.server_response.LaboratoryTransferResult")]
259pub struct LaboratoryTransferResult {
260    pub bytes: u64,
261}
262
263/// One queued row's payload + its content-slot ids.
264#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
265#[schemars(rename = "client_objectiveai_mcp.server_response.ReadMessageQueueRow")]
266pub struct ReadMessageQueueRow {
267    /// `message_queue_contents.id` of every slot in this row, in
268    /// part order. Matches `rich_content`'s part count 1:1.
269    pub content_ids: Vec<i64>,
270    /// The row's content as the CLI reconstructed it.
271    pub rich_content: crate::agent::completions::message::RichContent,
272}
273
274/// The successful `Initialize` payload — the upstream's verbatim
275/// `InitializeResult` plus the native `Mcp-Session-Id` the CLI got
276/// back from dialing the actual MCP server. The API forwards both
277/// to the proxy: the result as the JSON-RPC body, the session id as
278/// the `Mcp-Session-Id` response header. The CLI is a pure medium —
279/// it doesn't synthesize capabilities, doesn't name itself, doesn't
280/// pin a protocol version. Whatever the upstream MCP advertised is
281/// what the proxy sees.
282#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
283#[schemars(rename = "client_objectiveai_mcp.server_response.InitializeReply")]
284pub struct InitializeReply {
285    /// Upstream's native `Mcp-Session-Id`. One per CLI-hosted MCP
286    /// server — no aggregation, no encoding.
287    pub mcp_session_id: String,
288    /// The upstream's verbatim `InitializeResult` (capabilities,
289    /// server info, protocol version). Returned as-is to the proxy.
290    pub result: crate::mcp::initialize_result::InitializeResult,
291}
292
293/// JSON-RPC result/error shape for every typed method. Mirrors
294/// the wire shape upstream MCP servers return (`{result: …}` on
295/// success, `{error: {code, message, data?}}` on failure) but typed
296/// at the SDK level instead of buried inside a `serde_json::Value`.
297#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
298#[schemars(rename = "client_objectiveai_mcp.server_response.JsonRpcResult.{R}", bound = "R: JsonSchema")]
299#[serde(tag = "kind", rename_all = "snake_case")]
300pub enum JsonRpcResult<R> {
301    /// Method returned a typed result.
302    #[schemars(title = "Ok")]
303    Ok { result: R },
304    /// Method returned a JSON-RPC error envelope.
305    #[schemars(title = "Err")]
306    Err {
307        code: i64,
308        message: String,
309        #[serde(default, skip_serializing_if = "Option::is_none")]
310        #[schemars(extend("omitempty" = true))]
311        data: Option<serde_json::Value>,
312    },
313}
314
315/// Successful payload for [`Payload::Script`].
316#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
317#[schemars(rename = "client_objectiveai_mcp.server_response.ScriptResult")]
318pub struct ScriptResult {
319    /// The script's output: the messages it appends to the
320    /// conversation. Assistant/tool roles only — a script never puts
321    /// words in the user's mouth.
322    pub messages: Vec<crate::agent::script::OutputMessage>,
323}