Skip to main content

starweaver_model/message/
response_parts.rs

1//! Canonical model response parts.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::{ProviderPartInfo, ToolCallPart};
7
8/// Response part returned by a model.
9#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
10#[serde(tag = "kind", rename_all = "snake_case")]
11pub enum ModelResponsePart {
12    /// Plain text output.
13    Text {
14        /// Output text.
15        text: String,
16    },
17    /// Plain text output with provider-private replay metadata.
18    ProviderText {
19        /// Output text.
20        text: String,
21        /// Provider replay metadata.
22        #[serde(default, skip_serializing_if = "ProviderPartInfo::is_empty")]
23        provider: ProviderPartInfo,
24    },
25    /// Reasoning or thinking output.
26    Thinking {
27        /// Thinking text.
28        text: String,
29        /// Provider signature where available.
30        #[serde(default, skip_serializing_if = "Option::is_none")]
31        signature: Option<String>,
32    },
33    /// Reasoning or thinking output with provider-private replay metadata.
34    ProviderThinking {
35        /// Thinking text or provider-provided reasoning summary.
36        text: String,
37        /// Provider signature or encrypted reasoning payload where available.
38        #[serde(default, skip_serializing_if = "Option::is_none")]
39        signature: Option<String>,
40        /// Provider replay metadata.
41        #[serde(default, skip_serializing_if = "ProviderPartInfo::is_empty")]
42        provider: ProviderPartInfo,
43    },
44    /// Provider-neutral tool call.
45    ToolCall(ToolCallPart),
46    /// Provider-neutral tool call with provider-private replay metadata.
47    ProviderToolCall {
48        /// Tool call visible to runtime tool execution.
49        call: ToolCallPart,
50        /// Provider replay metadata, including output item ID and namespaces.
51        #[serde(default, skip_serializing_if = "ProviderPartInfo::is_empty")]
52        provider: ProviderPartInfo,
53    },
54    /// Provider-native tool call payload.
55    NativeToolCall {
56        /// Native tool type.
57        tool_type: String,
58        /// Provider payload.
59        payload: Value,
60    },
61    /// Provider-native tool return payload.
62    NativeToolReturn {
63        /// Native tool type.
64        tool_type: String,
65        /// Provider payload.
66        payload: Value,
67    },
68    /// File returned by the model.
69    File {
70        /// File URL.
71        url: String,
72        /// File media type.
73        media_type: String,
74    },
75    /// Compaction summary.
76    Compaction {
77        /// Summary text.
78        summary: String,
79    },
80    /// Opaque provider output item kept for same-provider replay.
81    ProviderOpaque {
82        /// Provider output item type.
83        item_type: String,
84        /// Provider payload.
85        payload: Value,
86        /// Provider replay metadata.
87        #[serde(default, skip_serializing_if = "ProviderPartInfo::is_empty")]
88        provider: ProviderPartInfo,
89    },
90}
91
92impl ModelResponsePart {
93    /// Return text content for text-like parts.
94    #[must_use]
95    pub const fn text(&self) -> Option<&str> {
96        match self {
97            Self::Text { text } | Self::ProviderText { text, .. } => Some(text.as_str()),
98            Self::Thinking { .. }
99            | Self::ProviderThinking { .. }
100            | Self::ToolCall(_)
101            | Self::ProviderToolCall { .. }
102            | Self::NativeToolCall { .. }
103            | Self::NativeToolReturn { .. }
104            | Self::File { .. }
105            | Self::Compaction { .. }
106            | Self::ProviderOpaque { .. } => None,
107        }
108    }
109
110    /// Return thinking text and signature for reasoning-like parts.
111    #[must_use]
112    pub fn thinking(&self) -> Option<(&str, Option<&str>)> {
113        match self {
114            Self::Thinking { text, signature }
115            | Self::ProviderThinking {
116                text, signature, ..
117            } => Some((text.as_str(), signature.as_deref())),
118            Self::Text { .. }
119            | Self::ProviderText { .. }
120            | Self::ToolCall(_)
121            | Self::ProviderToolCall { .. }
122            | Self::NativeToolCall { .. }
123            | Self::NativeToolReturn { .. }
124            | Self::File { .. }
125            | Self::Compaction { .. }
126            | Self::ProviderOpaque { .. } => None,
127        }
128    }
129
130    /// Return a function-style tool call when present.
131    #[must_use]
132    pub const fn tool_call(&self) -> Option<&ToolCallPart> {
133        match self {
134            Self::ToolCall(call) | Self::ProviderToolCall { call, .. } => Some(call),
135            Self::Text { .. }
136            | Self::ProviderText { .. }
137            | Self::Thinking { .. }
138            | Self::ProviderThinking { .. }
139            | Self::NativeToolCall { .. }
140            | Self::NativeToolReturn { .. }
141            | Self::File { .. }
142            | Self::Compaction { .. }
143            | Self::ProviderOpaque { .. } => None,
144        }
145    }
146
147    /// Return provider replay metadata when present.
148    #[must_use]
149    pub const fn provider_part(&self) -> Option<&ProviderPartInfo> {
150        match self {
151            Self::ProviderText { provider, .. }
152            | Self::ProviderThinking { provider, .. }
153            | Self::ProviderToolCall { provider, .. }
154            | Self::ProviderOpaque { provider, .. } => Some(provider),
155            Self::Text { .. }
156            | Self::Thinking { .. }
157            | Self::ToolCall(_)
158            | Self::NativeToolCall { .. }
159            | Self::NativeToolReturn { .. }
160            | Self::File { .. }
161            | Self::Compaction { .. } => None,
162        }
163    }
164
165    /// Return true when this response part is a compaction boundary.
166    #[must_use]
167    pub fn is_compaction(&self) -> bool {
168        match self {
169            Self::Compaction { .. } => true,
170            Self::ProviderOpaque { item_type, .. } => item_type == "compaction",
171            Self::Text { .. }
172            | Self::ProviderText { .. }
173            | Self::Thinking { .. }
174            | Self::ProviderThinking { .. }
175            | Self::ToolCall(_)
176            | Self::ProviderToolCall { .. }
177            | Self::NativeToolCall { .. }
178            | Self::NativeToolReturn { .. }
179            | Self::File { .. } => false,
180        }
181    }
182}