Skip to main content

llmsdk_provider/language_model/
content.rs

1//! Content parts returned from `do_generate`.
2//!
3//! Mirrors `language-model-v4-content.ts` plus the leaf `-text`,
4//! `-reasoning`, `-file`, `-source`, `-tool-call`, `-tool-result`,
5//! `-tool-approval-request`, `-custom-content`, `-reasoning-file` files.
6// Rust guideline compliant 2026-02-21
7
8use serde::{Deserialize, Serialize};
9
10use crate::json::JsonValue;
11use crate::shared::{FileData, ProviderMetadata, ProviderOptions};
12
13use super::prompt::{FilePart, TextPart};
14
15/// One generated content unit.
16///
17/// Discriminated by `type` on the wire; identical tags to ai-sdk.
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19#[serde(tag = "type", rename_all = "kebab-case")]
20pub enum Content {
21    /// Plain text.
22    Text(TextPart),
23    /// Reasoning trace.
24    Reasoning(ReasoningPart),
25    /// Provider-specific opaque content.
26    Custom {
27        /// Custom kind tag, e.g. `"openai.web_search_result"`.
28        kind: String,
29        /// Provider-specific options.
30        #[serde(
31            default,
32            rename = "providerOptions",
33            skip_serializing_if = "Option::is_none"
34        )]
35        provider_options: Option<ProviderOptions>,
36    },
37    /// File generated as part of a reasoning trace.
38    ReasoningFile {
39        /// File payload.
40        data: FileData,
41        /// IANA media type.
42        #[serde(rename = "mediaType")]
43        media_type: String,
44        /// Provider-specific options.
45        #[serde(
46            default,
47            rename = "providerOptions",
48            skip_serializing_if = "Option::is_none"
49        )]
50        provider_options: Option<ProviderOptions>,
51    },
52    /// File generated by the model.
53    File(FilePart),
54    /// Tool the provider wants to execute, pending user approval.
55    ToolApprovalRequest(ToolApprovalRequest),
56    /// Citation / grounding source.
57    Source(Source),
58    /// Tool call requested by the model.
59    ToolCall(super::prompt::ToolCallPart),
60    /// Tool result reported by the provider (provider-executed tools).
61    ToolResult(ToolResult),
62}
63
64/// Reasoning trace produced by the model.
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
66pub struct ReasoningPart {
67    /// Reasoning text.
68    pub text: String,
69    /// Provider-specific options.
70    #[serde(
71        default,
72        rename = "providerOptions",
73        skip_serializing_if = "Option::is_none"
74    )]
75    pub provider_options: Option<ProviderOptions>,
76}
77
78/// Citation / grounding source returned by the model.
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80#[serde(tag = "sourceType", rename_all = "kebab-case")]
81pub enum Source {
82    /// Web URL source.
83    Url {
84        /// Source id.
85        id: String,
86        /// Absolute URL.
87        url: String,
88        /// Optional title.
89        #[serde(default, skip_serializing_if = "Option::is_none")]
90        title: Option<String>,
91        /// Provider-specific metadata.
92        #[serde(
93            default,
94            rename = "providerMetadata",
95            skip_serializing_if = "Option::is_none"
96        )]
97        provider_metadata: Option<ProviderMetadata>,
98    },
99    /// Document source (uploaded file).
100    Document {
101        /// Source id.
102        id: String,
103        /// IANA media type.
104        #[serde(rename = "mediaType")]
105        media_type: String,
106        /// Title.
107        title: String,
108        /// Optional filename.
109        #[serde(default, skip_serializing_if = "Option::is_none")]
110        filename: Option<String>,
111        /// Provider-specific metadata.
112        #[serde(
113            default,
114            rename = "providerMetadata",
115            skip_serializing_if = "Option::is_none"
116        )]
117        provider_metadata: Option<ProviderMetadata>,
118    },
119}
120
121/// Approval request for a provider-executed tool call.
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
123pub struct ToolApprovalRequest {
124    /// Unique approval id.
125    #[serde(rename = "approvalId")]
126    pub approval_id: String,
127    /// Tool call awaiting approval.
128    #[serde(rename = "toolCall")]
129    pub tool_call: super::prompt::ToolCallPart,
130    /// Provider-specific metadata.
131    #[serde(
132        default,
133        rename = "providerMetadata",
134        skip_serializing_if = "Option::is_none"
135    )]
136    pub provider_metadata: Option<ProviderMetadata>,
137}
138
139/// Tool result emitted by a provider-executed tool.
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
141pub struct ToolResult {
142    /// Id of the originating tool call.
143    #[serde(rename = "toolCallId")]
144    pub tool_call_id: String,
145    /// Tool name.
146    #[serde(rename = "toolName")]
147    pub tool_name: String,
148    /// Tool output.
149    pub output: ToolResultOutput,
150    /// Whether the tool result is preliminary.
151    ///
152    /// Preliminary tool results replace each other (e.g., image previews
153    /// during `image_generation_call.partial_image` events). A non-preliminary
154    /// result is always emitted before the model treats the tool call as
155    /// final. Mirrors upstream `LanguageModelV4ToolResult.preliminary`.
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub preliminary: Option<bool>,
158    /// Provider-specific metadata.
159    #[serde(
160        default,
161        rename = "providerMetadata",
162        skip_serializing_if = "Option::is_none"
163    )]
164    pub provider_metadata: Option<ProviderMetadata>,
165}
166
167/// Payload variants for a tool result.
168///
169/// Mirrors `LanguageModelV4ToolResultOutput`. `Content` carries a list of
170/// mixed text / file / custom parts.
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
172#[serde(tag = "type", rename_all = "kebab-case")]
173pub enum ToolResultOutput {
174    /// Text output, sent verbatim back to the model.
175    Text {
176        /// Text content.
177        value: String,
178        /// Provider-specific options.
179        #[serde(
180            default,
181            rename = "providerOptions",
182            skip_serializing_if = "Option::is_none"
183        )]
184        provider_options: Option<ProviderOptions>,
185    },
186    /// JSON output.
187    Json {
188        /// JSON value.
189        value: JsonValue,
190        /// Provider-specific options.
191        #[serde(
192            default,
193            rename = "providerOptions",
194            skip_serializing_if = "Option::is_none"
195        )]
196        provider_options: Option<ProviderOptions>,
197    },
198    /// User denied execution.
199    ExecutionDenied {
200        /// Optional reason.
201        #[serde(default, skip_serializing_if = "Option::is_none")]
202        reason: Option<String>,
203        /// Provider-specific options.
204        #[serde(
205            default,
206            rename = "providerOptions",
207            skip_serializing_if = "Option::is_none"
208        )]
209        provider_options: Option<ProviderOptions>,
210    },
211    /// Error reported as a string.
212    ErrorText {
213        /// Error text.
214        value: String,
215        /// Provider-specific options.
216        #[serde(
217            default,
218            rename = "providerOptions",
219            skip_serializing_if = "Option::is_none"
220        )]
221        provider_options: Option<ProviderOptions>,
222    },
223    /// Error reported as structured JSON.
224    ErrorJson {
225        /// Error payload.
226        value: JsonValue,
227        /// Provider-specific options.
228        #[serde(
229            default,
230            rename = "providerOptions",
231            skip_serializing_if = "Option::is_none"
232        )]
233        provider_options: Option<ProviderOptions>,
234    },
235    /// Multi-part output (text / file / custom).
236    Content {
237        /// Ordered output parts.
238        value: Vec<ToolOutputPart>,
239    },
240}
241
242/// One part inside [`ToolResultOutput::Content`].
243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
244#[serde(tag = "type", rename_all = "kebab-case")]
245pub enum ToolOutputPart {
246    /// Text fragment.
247    Text {
248        /// Text content.
249        text: String,
250        /// Provider-specific options.
251        #[serde(
252            default,
253            rename = "providerOptions",
254            skip_serializing_if = "Option::is_none"
255        )]
256        provider_options: Option<ProviderOptions>,
257    },
258    /// File fragment.
259    File {
260        /// File payload.
261        data: FileData,
262        /// IANA media type.
263        #[serde(rename = "mediaType")]
264        media_type: String,
265        /// Optional filename.
266        #[serde(default, skip_serializing_if = "Option::is_none")]
267        filename: Option<String>,
268        /// Provider-specific options.
269        #[serde(
270            default,
271            rename = "providerOptions",
272            skip_serializing_if = "Option::is_none"
273        )]
274        provider_options: Option<ProviderOptions>,
275    },
276    /// Custom opaque fragment.
277    Custom {
278        /// Provider-specific options.
279        #[serde(
280            default,
281            rename = "providerOptions",
282            skip_serializing_if = "Option::is_none"
283        )]
284        provider_options: Option<ProviderOptions>,
285    },
286}