Skip to main content

objectiveai_sdk/cli/command/
command_response.rs

1/// Convert a typed CLI response item into its MCP-shaped projection —
2/// either a media [`ContentBlock`] for MCP tool results or a JSONL
3/// `serde_json::Value` for the line-oriented stream wire format.
4/// Implementors are the per-leaf response shapes in the surrounding
5/// tree (`agents::spawn::ResponseItem`, `tools::run::ResponseItem`, …)
6/// — not yet wired up; the trait exists to anchor the contract.
7#[cfg(feature = "mcp")]
8pub trait CommandResponse {
9    fn into_mcp(self) -> McpResponseItem;
10}
11
12/// MCP-side projection of one CLI response item.
13///
14/// - [`McpResponseItem::Media`] carries a typed [`ContentBlock`] for
15///   image / audio / file payloads that ride the MCP tool result as
16///   real media blocks.
17/// - [`McpResponseItem::JSONL`] carries a `serde_json::Value` for the
18///   line-oriented JSONL wire format every other leaf emits.
19///
20/// Not serde-shaped on purpose: this is the runtime carrier between
21/// `CommandResponse::into_mcp` and the formatter; nothing on either
22/// side needs to round-trip it through JSON.
23#[cfg(feature = "mcp")]
24#[derive(Debug, Clone)]
25pub enum McpResponseItem {
26    Media(crate::mcp::tool::ContentBlock),
27    JSONL(serde_json::Value),
28}
29
30// Bare-media leaf impls. Every log media leaf under
31// `cli/command/logs/agents/completions/{request,response}/messages/.../{get,subscribe}`
32// declares `pub type Response = <media type>` for one of the four media
33// types below, so these four impls cover all 20 such leaves through the
34// type aliases. Each `self.into()` reaches the matching
35// `From<T> for ContentBlock` impl in `mcp/tool/content_block.rs`.
36
37#[cfg(feature = "mcp")]
38impl CommandResponse for crate::agent::completions::message::ImageUrl {
39    fn into_mcp(self) -> McpResponseItem {
40        McpResponseItem::Media(self.into())
41    }
42}
43
44#[cfg(feature = "mcp")]
45impl CommandResponse for crate::agent::completions::message::InputAudio {
46    fn into_mcp(self) -> McpResponseItem {
47        McpResponseItem::Media(self.into())
48    }
49}
50
51#[cfg(feature = "mcp")]
52impl CommandResponse for crate::agent::completions::message::VideoUrl {
53    fn into_mcp(self) -> McpResponseItem {
54        McpResponseItem::Media(self.into())
55    }
56}
57
58#[cfg(feature = "mcp")]
59impl CommandResponse for crate::agent::completions::message::File {
60    fn into_mcp(self) -> McpResponseItem {
61        McpResponseItem::Media(self.into())
62    }
63}
64
65// `RichContentPart` is a sum over the four media types above plus
66// `Text(String)`. Used directly as the `agents queue read id` leaf's
67// `Response` type so the queue-content reader's wire shape is bit-
68// identical to a rich-content part. Each arm delegates to the inner
69// type's impl: media variants pick up
70// `McpResponseItem::Media(ContentBlock::…)`, `Text` picks up
71// `Value::String` via the `String` impl below.
72#[cfg(feature = "mcp")]
73impl CommandResponse for crate::agent::completions::message::RichContentPart {
74    fn into_mcp(self) -> McpResponseItem {
75        use crate::agent::completions::message::RichContentPart;
76        match self {
77            RichContentPart::Text { text } => text.into_mcp(),
78            RichContentPart::ImageUrl { image_url } => image_url.into_mcp(),
79            RichContentPart::InputAudio { input_audio } => input_audio.into_mcp(),
80            RichContentPart::InputVideo { video_url } => video_url.into_mcp(),
81            RichContentPart::VideoUrl { video_url } => video_url.into_mcp(),
82            RichContentPart::File { file } => file.into_mcp(),
83        }
84    }
85}
86
87// JSONL passthrough — `serde_json::to_value(self)` is the body for
88// every alias target whose leaf simply emits its serde-shaped value
89// as one JSONL line. Special-cased shortcuts: `String` constructs
90// `Value::String` directly; `serde_json::Value` rides through as-is;
91// `Option<T>` delegates to `T::into_mcp` on `Some` and emits a JSONL
92// null on `None`.
93
94#[cfg(feature = "mcp")]
95impl CommandResponse for String {
96    fn into_mcp(self) -> McpResponseItem {
97        McpResponseItem::JSONL(serde_json::Value::String(self))
98    }
99}
100
101#[cfg(feature = "mcp")]
102impl CommandResponse for serde_json::Value {
103    fn into_mcp(self) -> McpResponseItem {
104        McpResponseItem::JSONL(self)
105    }
106}
107
108#[cfg(feature = "mcp")]
109impl CommandResponse for schemars::Schema {
110    fn into_mcp(self) -> McpResponseItem {
111        McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
112    }
113}
114
115#[cfg(feature = "mcp")]
116impl CommandResponse for crate::cli::command::ResponseSchema {
117    fn into_mcp(self) -> McpResponseItem {
118        self.0.into_mcp()
119    }
120}
121
122#[cfg(feature = "mcp")]
123impl<T: CommandResponse> CommandResponse for Option<T> {
124    fn into_mcp(self) -> McpResponseItem {
125        match self {
126            Some(v) => v.into_mcp(),
127            None => McpResponseItem::JSONL(serde_json::Value::Null),
128        }
129    }
130}
131
132#[cfg(feature = "mcp")]
133impl<T: CommandResponse> CommandResponse for Result<T, crate::cli::Error> {
134    fn into_mcp(self) -> McpResponseItem {
135        match self {
136            Ok(v) => v.into_mcp(),
137            Err(e) => e.into_mcp(),
138        }
139    }
140}
141
142#[cfg(feature = "mcp")]
143impl CommandResponse for crate::agent::completions::message::AssistantToolCallDelta {
144    fn into_mcp(self) -> McpResponseItem {
145        McpResponseItem::JSONL(
146            serde_json::to_value(self).unwrap(),
147        )
148    }
149}
150
151#[cfg(feature = "mcp")]
152impl CommandResponse for crate::agent::completions::message::AssistantToolCall {
153    fn into_mcp(self) -> McpResponseItem {
154        McpResponseItem::JSONL(
155            serde_json::to_value(self).unwrap(),
156        )
157    }
158}
159
160#[cfg(feature = "mcp")]
161impl CommandResponse for crate::agent::completions::response::Logprobs {
162    fn into_mcp(self) -> McpResponseItem {
163        McpResponseItem::JSONL(
164            serde_json::to_value(self).unwrap(),
165        )
166    }
167}
168
169#[cfg(feature = "mcp")]
170impl CommandResponse for crate::agent::response::GetAgentResponse {
171    fn into_mcp(self) -> McpResponseItem {
172        McpResponseItem::JSONL(
173            serde_json::to_value(self).unwrap(),
174        )
175    }
176}
177
178#[cfg(feature = "mcp")]
179impl CommandResponse for crate::functions::profiles::response::GetProfileResponse {
180    fn into_mcp(self) -> McpResponseItem {
181        McpResponseItem::JSONL(
182            serde_json::to_value(self).unwrap(),
183        )
184    }
185}
186
187#[cfg(feature = "mcp")]
188impl CommandResponse for crate::functions::response::GetFunctionResponse {
189    fn into_mcp(self) -> McpResponseItem {
190        McpResponseItem::JSONL(
191            serde_json::to_value(self).unwrap(),
192        )
193    }
194}
195
196#[cfg(feature = "mcp")]
197impl CommandResponse for crate::Remote {
198    fn into_mcp(self) -> McpResponseItem {
199        McpResponseItem::JSONL(
200            serde_json::to_value(self).unwrap(),
201        )
202    }
203}
204
205#[cfg(feature = "mcp")]
206impl CommandResponse for crate::swarm::response::GetSwarmResponse {
207    fn into_mcp(self) -> McpResponseItem {
208        McpResponseItem::JSONL(
209            serde_json::to_value(self).unwrap(),
210        )
211    }
212}
213
214#[cfg(feature = "mcp")]
215impl CommandResponse for crate::vector::completions::request::VectorCompletionCreateParams {
216    fn into_mcp(self) -> McpResponseItem {
217        McpResponseItem::JSONL(
218            serde_json::to_value(self).unwrap(),
219        )
220    }
221}
222