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 /// Acknowledges
96 /// [`super::super::server_request::Payload::Drop`]. Infallible — no
97 /// `JsonRpcResult` wrapper; carries `dropped`: whether a bucket for
98 /// the response id was present and removed. Non-MCP — no `mcp_kind`.
99 #[schemars(title = "Drop")]
100 Drop(DropResult),
101
102 /// Reply to
103 /// [`super::super::server_request::Payload::LaboratoryTransfer`].
104 /// On success carries the byte count streamed; on failure the
105 /// conduit's transfer error. Non-MCP — no `mcp_kind`.
106 #[schemars(title = "LaboratoryTransfer")]
107 LaboratoryTransfer(JsonRpcResult<LaboratoryTransferResult>),
108}
109
110impl Payload {
111 /// Which CLI-hosted MCP server produced this reply. `Some` for
112 /// MCP-routed variants (echoes the request's `mcp_kind`); `None`
113 /// for `ReadMessageQueue`.
114 pub fn mcp_kind(&self) -> Option<super::super::McpKind> {
115 match self {
116 Payload::Initialize { mcp_kind, .. }
117 | Payload::ToolsList { mcp_kind, .. }
118 | Payload::ToolsCall { mcp_kind, .. }
119 | Payload::ResourcesList { mcp_kind, .. }
120 | Payload::ResourcesRead { mcp_kind, .. }
121 | Payload::SessionTerminate { mcp_kind, .. } => Some(mcp_kind.clone()),
122 Payload::ReadMessageQueue(_)
123 | Payload::Retrieve(_)
124 | Payload::Drop(_)
125 | Payload::LaboratoryTransfer(_) => None,
126 }
127 }
128}
129
130/// Successful payload for [`Payload::ReadMessageQueue`].
131///
132/// One [`ReadMessageQueueRow`] per consumed `message_queue` row,
133/// in oldest-first id order. Each row's `content_ids` are the
134/// `message_queue_contents.id`s for that row's slots; the row's
135/// `rich_content` is the CLI's reconstructed payload for that
136/// row alone (no cross-row separator splicing — callers join if
137/// they want a unified User message).
138///
139/// Two consumers:
140/// - **Startup snapshot** (`run_agent_loop`): joins every row's
141/// parts with `"\n\n"` separators, flattens `content_ids`, and
142/// stamps the result onto the first
143/// `AssistantResponseChunk.request_message_ids`.
144/// - **`ApiQueueDelegate`** (`agents logs read subscribe`-style
145/// per-tool-response delivery): keeps rows separate so each
146/// gets converted to its own `Vec<ContentBlock>` and surfaces
147/// row-by-row on tool responses.
148///
149/// The downstream LogWriter resolves each id's kind at write
150/// time (SQL CASE against `message_queue_contents.kind`) to
151/// dispatch the right `logs.message_table` variant — kinds don't
152/// need to ride on the wire.
153#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
154#[schemars(rename = "client_objectiveai_mcp.server_response.ReadMessageQueueResult")]
155pub struct ReadMessageQueueResult {
156 pub rows: Vec<ReadMessageQueueRow>,
157}
158
159/// Result of [`Payload::Drop`]. `dropped` is `true` if a connection
160/// bucket for the response id was present and removed, `false` if no
161/// bucket existed (the drop is idempotent either way).
162#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
163#[schemars(rename = "client_objectiveai_mcp.server_response.DropResult")]
164pub struct DropResult {
165 pub dropped: bool,
166}
167
168/// Successful payload for [`Payload::LaboratoryTransfer`] — the number of
169/// archive bytes streamed from the source laboratory into the destination.
170#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
171#[schemars(rename = "client_objectiveai_mcp.server_response.LaboratoryTransferResult")]
172pub struct LaboratoryTransferResult {
173 pub bytes: u64,
174}
175
176/// One queued row's payload + its content-slot ids.
177#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
178#[schemars(rename = "client_objectiveai_mcp.server_response.ReadMessageQueueRow")]
179pub struct ReadMessageQueueRow {
180 /// `message_queue_contents.id` of every slot in this row, in
181 /// part order. Matches `rich_content`'s part count 1:1.
182 pub content_ids: Vec<i64>,
183 /// The row's content as the CLI reconstructed it.
184 pub rich_content: crate::agent::completions::message::RichContent,
185}
186
187/// The successful `Initialize` payload — the upstream's verbatim
188/// `InitializeResult` plus the native `Mcp-Session-Id` the CLI got
189/// back from dialing the actual MCP server. The API forwards both
190/// to the proxy: the result as the JSON-RPC body, the session id as
191/// the `Mcp-Session-Id` response header. The CLI is a pure medium —
192/// it doesn't synthesize capabilities, doesn't name itself, doesn't
193/// pin a protocol version. Whatever the upstream MCP advertised is
194/// what the proxy sees.
195#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
196#[schemars(rename = "client_objectiveai_mcp.server_response.InitializeReply")]
197pub struct InitializeReply {
198 /// Upstream's native `Mcp-Session-Id`. One per CLI-hosted MCP
199 /// server — no aggregation, no encoding.
200 pub mcp_session_id: String,
201 /// The upstream's verbatim `InitializeResult` (capabilities,
202 /// server info, protocol version). Returned as-is to the proxy.
203 pub result: crate::mcp::initialize_result::InitializeResult,
204}
205
206/// JSON-RPC result/error shape for every typed method. Mirrors
207/// the wire shape upstream MCP servers return (`{result: …}` on
208/// success, `{error: {code, message, data?}}` on failure) but typed
209/// at the SDK level instead of buried inside a `serde_json::Value`.
210#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
211#[schemars(rename = "client_objectiveai_mcp.server_response.JsonRpcResult.{R}", bound = "R: JsonSchema")]
212#[serde(tag = "kind", rename_all = "snake_case")]
213pub enum JsonRpcResult<R> {
214 /// Method returned a typed result.
215 #[schemars(title = "Ok")]
216 Ok { result: R },
217 /// Method returned a JSON-RPC error envelope.
218 #[schemars(title = "Err")]
219 Err {
220 code: i64,
221 message: String,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
223 #[schemars(extend("omitempty" = true))]
224 data: Option<serde_json::Value>,
225 },
226}