Skip to main content

dynamo_protocols/types/responses/
mod.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Dynamo owns the Responses-API input-side type chain. Upstream async-openai
5// is the source for everything else (output-side types, streaming events,
6// individual tool-call payloads, etc.).
7//
8// The input chain is owned because upstream marks fields as required that
9// real-world clients (OpenAI Agents SDK, Codex, etc.) routinely omit when
10// round-tripping a prior assistant turn as input:
11//   - `OutputMessage.id` / `.status` — omitted when echoing a previous output
12//   - `OutputTextContent.annotations` — omitted when the part carried none
13// Upstream is slow to relax these (see e.g. 64bit/async-openai#535 for the
14// sibling `ReasoningItem.id` fix, still open at time of writing); OpenAI's own
15// hosted API accepts the relaxed shapes on input regardless.
16//
17// This mirrors the pattern in `crate::types::chat` where Dynamo owns the
18// request types it needs to extend or relax while re-exporting the rest of
19// upstream's type library verbatim.
20//
21// Naming: the relaxed assistant-input message is `InputOutputMessage` (and
22// `InputOutputMessageContent` / `InputOutputTextContent` for its content
23// parts) to avoid colliding with upstream's `OutputMessage`, which remains the
24// canonical type for *output-side* response construction (`OutputItem`,
25// `Response.output`). `MessageItem`, `Item`, `InputItem`, `InputParam`, and
26// `CreateResponse` are input-only and shadow upstream's same-named types
27// without conflict.
28
29use std::collections::HashMap;
30
31use serde::{Deserialize, Serialize};
32
33// Re-export all upstream response types (shared structures like ResponseUsage,
34// tool-call item types, streaming events, etc.). The types we own below
35// shadow their upstream counterparts where no dual-side conflict exists.
36pub use async_openai::types::responses::*;
37
38// Re-export upstream's pre-shadow `InputContent` under an explicit alias.
39// Needed because `FunctionCallOutput::Content` and `EasyInputContent::ContentList`
40// are non-owned upstream types that carry upstream's original `InputContent`
41// inline, so downstream consumers occasionally need to name it alongside the
42// Dynamo-owned shadow defined further down this module.
43pub use async_openai::types::responses::InputContent as UpstreamInputContent;
44
45// Re-export from parent module for backward compat.
46pub use crate::types::ImageDetail;
47pub use crate::types::ReasoningEffort;
48pub use crate::types::ResponseFormatJsonSchema;
49
50// Backward-compatible type aliases for Dynamo consumer code migration.
51pub type Input = InputParam;
52pub type PromptConfig = Prompt;
53pub type TextConfig = ResponseTextParam;
54pub type TextResponseFormat = TextResponseFormatConfiguration;
55
56/// Stream of response events.
57pub type ResponseStream = std::pin::Pin<
58    Box<dyn futures::Stream<Item = Result<ResponseStreamEvent, crate::error::OpenAIError>> + Send>,
59>;
60
61/// Fields on upstream `Response` that the OpenResponses spec requires as
62/// `T | null` but async-openai declares as `Option<T>` with
63/// `skip_serializing_if = Option::is_none` — meaning `None` disappears from
64/// the wire shape, where the spec wants an explicit `null`.
65///
66/// Colocated here (next to the upstream `Response` re-export) rather than in
67/// `lib/llm/src/protocols/openai/responses/mod.rs` so that when upstream's
68/// `Response` gains a new nullable-required field, the reviewer editing this
69/// module is looking directly at the authoritative list. Keep sorted
70/// alphabetically; entries must match serde field names on `Response` exactly.
71///
72/// Any field we unconditionally populate ourselves during response
73/// construction (e.g. `metadata`, `parallel_tool_calls`, `temperature`,
74/// `text`, `tool_choice`, `tools`, `top_p`, `top_logprobs`, `truncation`,
75/// `service_tier`, `background`) is deliberately absent — it's always
76/// present on the wire, so listing it here would be noise.
77pub const SPEC_NULLABLE_REQUIRED_RESPONSE_FIELDS: &[&str] = &[
78    "billing",
79    "completed_at",
80    "conversation",
81    "error",
82    "incomplete_details",
83    "instructions",
84    "max_output_tokens",
85    "max_tool_calls",
86    "previous_response_id",
87    "prompt",
88    "prompt_cache_key",
89    "prompt_cache_retention",
90    "reasoning",
91    "safety_identifier",
92    "usage",
93];
94
95// ---------------------------------------------------------------------------
96// Input-side assistant message (relaxed vs upstream OutputMessage)
97// ---------------------------------------------------------------------------
98
99/// Deserialize `null` or a missing field as the default empty `Vec`. Plain
100/// `#[serde(default)]` only fires when the field is absent; explicit `null`
101/// would otherwise fail `Vec::deserialize`. Clients (notably some Agents SDK
102/// variants) have been observed to send `"annotations": null`, so treat
103/// omission and explicit null the same.
104fn deserialize_null_as_empty_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
105where
106    T: Deserialize<'de>,
107    D: serde::Deserializer<'de>,
108{
109    Option::<Vec<T>>::deserialize(deserializer).map(Option::unwrap_or_default)
110}
111
112/// Deserialize `null` or a missing field as `T::default()`. Scalar counterpart
113/// to `deserialize_null_as_empty_vec` — plain `#[serde(default)]` rejects
114/// explicit `null` because serde tries to deserialize the null into `T` and
115/// fails. Real clients emit `null` for unset enum-ish fields (e.g. OpenAI
116/// Agents SDK sending `"detail": null` on `input_image` parts).
117fn deserialize_null_as_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>
118where
119    T: Deserialize<'de> + Default,
120    D: serde::Deserializer<'de>,
121{
122    Option::<T>::deserialize(deserializer).map(Option::unwrap_or_default)
123}
124
125/// Relaxed counterpart to upstream `OutputTextContent` for input-side content.
126/// `annotations` tolerates both missing and explicit `null`; upstream requires
127/// it to be a present non-null array.
128#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
129pub struct InputOutputTextContent {
130    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
131    pub annotations: Vec<Annotation>,
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub logprobs: Option<Vec<LogProb>>,
134    pub text: String,
135}
136
137/// Content parts of a prior assistant message presented as input.
138#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
139#[serde(tag = "type", rename_all = "snake_case")]
140pub enum InputOutputMessageContent {
141    OutputText(InputOutputTextContent),
142    Refusal(RefusalContent),
143}
144
145/// An assistant message echoed back as input for a subsequent turn. Relaxed
146/// compared to upstream `OutputMessage`: `id`, `status`, and `content` are all
147/// optional. Some clients send a bare assistant shell (`{"type":"message",
148/// "role":"assistant"}`) with no `content` at all, usually on pure tool-call
149/// turns; treat absent `content` as an empty vec, same way we treat a missing
150/// `id`/`status`.
151#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
152pub struct InputOutputMessage {
153    #[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
154    pub content: Vec<InputOutputMessageContent>,
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub id: Option<String>,
157    pub role: AssistantRole,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub phase: Option<MessagePhase>,
160    #[serde(default, skip_serializing_if = "Option::is_none")]
161    pub status: Option<OutputStatus>,
162}
163
164// ---------------------------------------------------------------------------
165// Input-side image / content / message (shadow upstream, relaxed shapes)
166// ---------------------------------------------------------------------------
167
168/// Relaxed counterpart to upstream `InputImageContent`. `detail` defaults to
169/// `ImageDetail::Auto` when the client omits it — OpenAI's hosted API and the
170/// OpenResponses spec both accept this shape, but upstream's struct marks
171/// `detail` as required.
172#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
173pub struct InputImageContent {
174    #[serde(default, deserialize_with = "deserialize_null_as_default")]
175    pub detail: ImageDetail,
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub file_id: Option<String>,
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    pub image_url: Option<String>,
180}
181
182/// Parts of an input message: text, image, or file. Mirrors upstream
183/// `InputContent` but routes `InputImage` through the Dynamo-owned relaxed
184/// `InputImageContent` above.
185#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
186#[serde(tag = "type", rename_all = "snake_case")]
187pub enum InputContent {
188    InputText(InputTextContent),
189    InputImage(InputImageContent),
190    InputFile(InputFileContent),
191}
192
193/// User / system / developer input message. Shadows upstream `InputMessage`
194/// so we can route through the Dynamo-owned `InputContent` chain.
195#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
196pub struct InputMessage {
197    pub content: Vec<InputContent>,
198    pub role: InputRole,
199    #[serde(default, skip_serializing_if = "Option::is_none")]
200    pub status: Option<OutputStatus>,
201}
202
203// ---------------------------------------------------------------------------
204// Input-side Item / Message / InputItem / InputParam (shadow upstream)
205// ---------------------------------------------------------------------------
206
207/// Message item within `Item`. Untagged; disambiguated by the `role` field:
208/// the `Output` variant requires `role: "assistant"` (via `AssistantRole`,
209/// which is a single-variant enum) and `Input` requires `role` in
210/// `"user" | "system" | "developer"` (via `InputRole`). A payload with an
211/// unknown role (e.g. `"tool"`) or a missing `role` produces the generic
212/// untagged-enum error — callers are expected to send a valid role. If you
213/// see the "data did not match any variant of untagged enum" failure on this
214/// type, it is almost always a role mismatch.
215#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
216#[serde(untagged)]
217pub enum MessageItem {
218    /// Prior assistant output echoed back (role: assistant). Tried first — its
219    /// `role` constraint excludes user/system/developer inputs.
220    Output(InputOutputMessage),
221    /// User / system / developer input message.
222    Input(InputMessage),
223}
224
225/// Structured input/output item, discriminated by `type`. Mirrors upstream
226/// `Item` variant-for-variant; only `Message` uses a Dynamo-owned type.
227#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
228#[serde(tag = "type", rename_all = "snake_case")]
229pub enum Item {
230    Message(MessageItem),
231    FileSearchCall(FileSearchToolCall),
232    ComputerCall(ComputerToolCall),
233    ComputerCallOutput(ComputerCallOutputItemParam),
234    WebSearchCall(WebSearchToolCall),
235    FunctionCall(FunctionToolCall),
236    FunctionCallOutput(FunctionCallOutputItemParam),
237    ToolSearchCall(ToolSearchCallItemParam),
238    ToolSearchOutput(ToolSearchOutputItemParam),
239    Reasoning(ReasoningItem),
240    Compaction(CompactionSummaryItemParam),
241    ImageGenerationCall(ImageGenToolCall),
242    CodeInterpreterCall(CodeInterpreterToolCall),
243    LocalShellCall(LocalShellToolCall),
244    LocalShellCallOutput(LocalShellToolCallOutput),
245    ShellCall(FunctionShellCallItemParam),
246    ShellCallOutput(FunctionShellCallOutputItemParam),
247    ApplyPatchCall(ApplyPatchToolCallItemParam),
248    ApplyPatchCallOutput(ApplyPatchToolCallOutputItemParam),
249    McpListTools(MCPListTools),
250    McpApprovalRequest(MCPApprovalRequest),
251    McpApprovalResponse(MCPApprovalResponse),
252    McpCall(MCPToolCall),
253    CustomToolCallOutput(CustomToolCallOutput),
254    CustomToolCall(CustomToolCall),
255}
256
257/// Single input item. Untagged; order matters (most specific first).
258#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
259#[serde(untagged)]
260pub enum InputItem {
261    ItemReference(ItemReference),
262    Item(Item),
263    EasyMessage(EasyInputMessage),
264}
265
266/// Input to a `POST /v1/responses` request.
267#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
268#[serde(untagged)]
269pub enum InputParam {
270    Text(String),
271    Items(Vec<InputItem>),
272}
273
274impl Default for InputParam {
275    fn default() -> Self {
276        Self::Text(String::new())
277    }
278}
279
280// ---------------------------------------------------------------------------
281// CreateResponse (owned, uses Dynamo-owned InputParam)
282// ---------------------------------------------------------------------------
283
284/// Request body for `POST /v1/responses`. Mirrors upstream `CreateResponse`
285/// field-for-field but uses Dynamo-owned `InputParam`, which transitively
286/// accepts the relaxed input shapes described in this module's header. All
287/// other fields reference upstream types verbatim.
288#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
289pub struct CreateResponse {
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub background: Option<bool>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub conversation: Option<ConversationParam>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub include: Option<Vec<IncludeEnum>>,
296    pub input: InputParam,
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub instructions: Option<String>,
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub max_output_tokens: Option<u32>,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub max_tool_calls: Option<u32>,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub metadata: Option<HashMap<String, String>>,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub model: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub parallel_tool_calls: Option<bool>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub previous_response_id: Option<String>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub prompt: Option<Prompt>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub prompt_cache_key: Option<String>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub prompt_cache_retention: Option<PromptCacheRetention>,
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub reasoning: Option<Reasoning>,
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub safety_identifier: Option<String>,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub service_tier: Option<ServiceTier>,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub store: Option<bool>,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub stream: Option<bool>,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub stream_options: Option<ResponseStreamOptions>,
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub temperature: Option<f32>,
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub text: Option<ResponseTextParam>,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub tool_choice: Option<ToolChoiceParam>,
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub tools: Option<Vec<Tool>>,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub top_logprobs: Option<u8>,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub top_p: Option<f32>,
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub truncation: Option<Truncation>,
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn relaxed_assistant_message_without_id_or_status() {
351        let json = serde_json::json!({
352            "type": "message",
353            "role": "assistant",
354            "content": [{"type": "output_text", "text": "hi"}]
355        });
356        let item: InputItem = serde_json::from_value(json).unwrap();
357        match item {
358            InputItem::Item(Item::Message(MessageItem::Output(out))) => {
359                assert_eq!(out.role, AssistantRole::Assistant);
360                assert!(out.id.is_none());
361                assert!(out.status.is_none());
362            }
363            other => panic!("expected Item::Message(Output), got {other:?}"),
364        }
365    }
366
367    #[test]
368    fn input_image_without_detail_defaults_to_auto() {
369        let json = serde_json::json!({
370            "type": "input_image",
371            "image_url": "https://example.com/cat.jpg"
372        });
373        let content: InputContent = serde_json::from_value(json).unwrap();
374        match content {
375            InputContent::InputImage(img) => assert_eq!(img.detail, ImageDetail::Auto),
376            other => panic!("expected InputImage, got {other:?}"),
377        }
378    }
379
380    #[test]
381    fn input_image_with_explicit_null_detail_defaults_to_auto() {
382        let json = serde_json::json!({
383            "type": "input_image",
384            "image_url": "https://example.com/cat.jpg",
385            "detail": null
386        });
387        let content: InputContent = serde_json::from_value(json).unwrap();
388        match content {
389            InputContent::InputImage(img) => assert_eq!(img.detail, ImageDetail::Auto),
390            other => panic!("expected InputImage, got {other:?}"),
391        }
392    }
393
394    #[test]
395    fn assistant_message_without_content_field_deserializes() {
396        // Bare assistant shell — no `content` field at all. Seen in real
397        // Codex/Agents-SDK traffic on pure tool-call turns. `#[serde(default)]`
398        // on `content` must accept omission and yield an empty vec.
399        let json = serde_json::json!({
400            "type": "message",
401            "role": "assistant"
402        });
403        let item: InputItem = serde_json::from_value(json).unwrap();
404        match item {
405            InputItem::Item(Item::Message(MessageItem::Output(out))) => {
406                assert_eq!(out.role, AssistantRole::Assistant);
407                assert!(out.content.is_empty());
408                assert!(out.id.is_none());
409                assert!(out.status.is_none());
410            }
411            other => panic!("expected Item::Message(Output), got {other:?}"),
412        }
413    }
414
415    #[test]
416    fn assistant_message_with_explicit_null_content_deserializes() {
417        // Mirrors the `annotations: null` case: some serializers emit JSON null
418        // for absent fields instead of omitting them. `Vec::deserialize` rejects
419        // null, so `content` also needs `deserialize_null_as_empty_vec`.
420        let json = serde_json::json!({
421            "type": "message",
422            "role": "assistant",
423            "content": null
424        });
425        let item: InputItem = serde_json::from_value(json).unwrap();
426        match item {
427            InputItem::Item(Item::Message(MessageItem::Output(out))) => {
428                assert!(out.content.is_empty());
429            }
430            other => panic!("expected Item::Message(Output), got {other:?}"),
431        }
432    }
433
434    #[test]
435    fn mcp_call_item_deserializes() {
436        // Guards against Item variant drift vs upstream — MCP item types were
437        // added after the initial owned `Item` chain landed.
438        let json = serde_json::json!({
439            "type": "mcp_call",
440            "id": "mcp_1",
441            "server_label": "srv",
442            "name": "t",
443            "arguments": "{}"
444        });
445        let item: InputItem = serde_json::from_value(json).unwrap();
446        assert!(matches!(item, InputItem::Item(Item::McpCall(_))));
447    }
448
449    #[test]
450    fn strict_assistant_message_still_deserializes() {
451        let json = serde_json::json!({
452            "type": "message",
453            "role": "assistant",
454            "id": "msg_1",
455            "status": "completed",
456            "content": [{"type": "output_text", "text": "hi", "annotations": []}]
457        });
458        let item: InputItem = serde_json::from_value(json).unwrap();
459        match item {
460            InputItem::Item(Item::Message(MessageItem::Output(out))) => {
461                assert_eq!(out.id.as_deref(), Some("msg_1"));
462                assert_eq!(out.status, Some(OutputStatus::Completed));
463            }
464            other => panic!("expected Item::Message(Output), got {other:?}"),
465        }
466    }
467
468    #[test]
469    fn user_message_routes_to_input_variant() {
470        let json = serde_json::json!({
471            "type": "message",
472            "role": "user",
473            "content": [{"type": "input_text", "text": "hi"}]
474        });
475        let item: InputItem = serde_json::from_value(json).unwrap();
476        assert!(matches!(
477            item,
478            InputItem::Item(Item::Message(MessageItem::Input(_)))
479        ));
480    }
481
482    #[test]
483    fn function_call_item_still_deserializes() {
484        let json = serde_json::json!({
485            "type": "function_call",
486            "call_id": "c",
487            "name": "f",
488            "arguments": "{}"
489        });
490        let item: InputItem = serde_json::from_value(json).unwrap();
491        assert!(matches!(item, InputItem::Item(Item::FunctionCall(_))));
492    }
493
494    #[test]
495    fn easy_message_string_content_routes_to_easymessage() {
496        let json = serde_json::json!({"role": "assistant", "content": "x"});
497        let item: InputItem = serde_json::from_value(json).unwrap();
498        assert!(matches!(item, InputItem::EasyMessage(_)));
499    }
500
501    #[test]
502    fn output_text_without_annotations_defaults_empty() {
503        let json = serde_json::json!({"type": "output_text", "text": "hi"});
504        let part: InputOutputMessageContent = serde_json::from_value(json).unwrap();
505        match part {
506            InputOutputMessageContent::OutputText(t) => {
507                assert!(t.annotations.is_empty());
508            }
509            _ => panic!("expected OutputText"),
510        }
511    }
512
513    #[test]
514    fn output_text_with_explicit_null_annotations_deserializes_as_empty() {
515        // Some clients serialize absent fields as JSON null instead of omitting
516        // them. `Vec::deserialize` would reject null; the custom deserializer
517        // treats explicit null identically to a missing field.
518        let json = serde_json::json!({"type": "output_text", "text": "hi", "annotations": null});
519        let part: InputOutputMessageContent = serde_json::from_value(json).unwrap();
520        match part {
521            InputOutputMessageContent::OutputText(t) => {
522                assert!(t.annotations.is_empty());
523            }
524            _ => panic!("expected OutputText"),
525        }
526    }
527
528    #[test]
529    fn assistant_message_with_explicit_null_id_and_status_deserializes() {
530        // `Option<T>` natively accepts null as `None`, so these explicit-null
531        // fields should flow through without a custom deserializer. This test
532        // pins that behavior against accidental regressions (e.g. if someone
533        // switches the field type away from `Option<_>`).
534        let json = serde_json::json!({
535            "type": "message",
536            "role": "assistant",
537            "id": null,
538            "status": null,
539            "content": [{"type": "output_text", "text": "hi", "annotations": null}]
540        });
541        let item: InputItem = serde_json::from_value(json).unwrap();
542        match item {
543            InputItem::Item(Item::Message(MessageItem::Output(out))) => {
544                assert!(out.id.is_none());
545                assert!(out.status.is_none());
546                assert_eq!(out.content.len(), 1);
547            }
548            other => panic!("expected Item::Message(Output), got {other:?}"),
549        }
550    }
551
552    #[test]
553    fn create_response_roundtrip_with_relaxed_input() {
554        let body = serde_json::json!({
555            "model": "m",
556            "input": [
557                {"type": "message", "role": "user", "content": [
558                    {"type": "input_text", "text": "hi"}
559                ]},
560                {"type": "function_call", "call_id": "c", "name": "f", "arguments": "{}"},
561                {"type": "message", "role": "assistant", "content": [
562                    {"type": "output_text", "text": "\n\n"}
563                ]},
564                {"type": "function_call_output", "call_id": "c", "output": "x"}
565            ]
566        });
567
568        let req: CreateResponse = serde_json::from_value(body).unwrap();
569        let items = match &req.input {
570            InputParam::Items(items) => items,
571            _ => panic!("expected Items"),
572        };
573        assert_eq!(items.len(), 4);
574        assert!(matches!(
575            items[2],
576            InputItem::Item(Item::Message(MessageItem::Output(_)))
577        ));
578    }
579}