Skip to main content

genai_rs/
steps.rs

1//! Step types for the Interactions API (revision 2026-05-20).
2//!
3//! Under API revision 2026-05-20 the response body carries a `steps` array
4//! instead of the launch-era `outputs` array. Each [`Step`] is a typed record
5//! of one unit of work in the interaction: user input, model output, internal
6//! reasoning (thoughts), and calls/results for client-side functions and
7//! server-side tools.
8//!
9//! Content (text, images, audio, video, documents) is nested inside the
10//! `user_input` / `model_output` steps as [`Content`] blocks.
11//!
12//! # Forward Compatibility
13//!
14//! [`Step`], [`StepDelta`] and [`FunctionResultPayload`] follow the
15//! [Evergreen](https://github.com/google-deepmind/evergreen-spec) philosophy:
16//! unrecognized wire data is preserved in `Unknown` variants rather than
17//! rejected.
18
19use serde::{Deserialize, Serialize};
20
21use crate::content::{
22    Annotation, CodeExecutionLanguage, Content, FileSearchResultItem, GoogleMapsResultItem,
23    GoogleSearchResultItem, Resolution, UrlContextResultItem,
24};
25
26// =============================================================================
27// StepError (wire: google.rpc.Status-like {code, message, details})
28// =============================================================================
29
30/// Error details attached to a `model_output` step.
31///
32/// Mirrors the API's status shape: `{code, message, details}`.
33#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
34#[serde(default)]
35pub struct StepError {
36    /// Numeric error code, if provided.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub code: Option<i64>,
39    /// Human-readable error message.
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub message: Option<String>,
42    /// Additional structured error details.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub details: Option<Vec<serde_json::Value>>,
45}
46
47// =============================================================================
48// FunctionResultPayload
49// =============================================================================
50
51/// The `result` of a `function_result` (or `mcp_server_tool_result`) step.
52///
53/// The wire format is a union: a plain string, an arbitrary JSON object, or a
54/// list of content blocks (text/image). This type models all three shapes and
55/// preserves anything else (numbers, booleans, mixed arrays) in the
56/// [`FunctionResultPayload::Json`] catch-all so no data is lost.
57#[derive(Clone, Debug, PartialEq)]
58#[non_exhaustive]
59pub enum FunctionResultPayload {
60    /// A plain string result.
61    Text(String),
62    /// A structured JSON result (object or any other non-string,
63    /// non-content-list JSON value). This doubles as the Evergreen catch-all
64    /// for shapes this library does not recognize.
65    Json(serde_json::Value),
66    /// A list of content blocks (e.g. text and images).
67    Contents(Vec<Content>),
68}
69
70impl FunctionResultPayload {
71    /// Returns the string result, if this is a `Text` payload.
72    #[must_use]
73    pub fn as_text(&self) -> Option<&str> {
74        match self {
75            Self::Text(t) => Some(t),
76            _ => None,
77        }
78    }
79
80    /// Returns the JSON value, if this is a `Json` payload.
81    #[must_use]
82    pub fn as_json(&self) -> Option<&serde_json::Value> {
83        match self {
84            Self::Json(v) => Some(v),
85            _ => None,
86        }
87    }
88
89    /// Returns the content blocks, if this is a `Contents` payload.
90    #[must_use]
91    pub fn as_contents(&self) -> Option<&[Content]> {
92        match self {
93            Self::Contents(c) => Some(c),
94            _ => None,
95        }
96    }
97
98    /// Converts the payload to a `serde_json::Value` (lossless).
99    #[must_use]
100    pub fn to_value(&self) -> serde_json::Value {
101        match self {
102            Self::Text(t) => serde_json::Value::String(t.clone()),
103            Self::Json(v) => v.clone(),
104            Self::Contents(c) => serde_json::to_value(c).unwrap_or(serde_json::Value::Null),
105        }
106    }
107}
108
109impl From<serde_json::Value> for FunctionResultPayload {
110    /// Converts an arbitrary JSON value into the appropriate payload variant.
111    ///
112    /// Strings become [`FunctionResultPayload::Text`]; everything else becomes
113    /// [`FunctionResultPayload::Json`].
114    fn from(value: serde_json::Value) -> Self {
115        match value {
116            serde_json::Value::String(s) => Self::Text(s),
117            other => Self::Json(other),
118        }
119    }
120}
121
122impl From<&str> for FunctionResultPayload {
123    fn from(value: &str) -> Self {
124        Self::Text(value.to_string())
125    }
126}
127
128impl From<String> for FunctionResultPayload {
129    fn from(value: String) -> Self {
130        Self::Text(value)
131    }
132}
133
134impl From<Vec<Content>> for FunctionResultPayload {
135    fn from(value: Vec<Content>) -> Self {
136        Self::Contents(value)
137    }
138}
139
140impl Serialize for FunctionResultPayload {
141    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142    where
143        S: serde::Serializer,
144    {
145        match self {
146            Self::Text(t) => serializer.serialize_str(t),
147            Self::Json(v) => v.serialize(serializer),
148            Self::Contents(c) => c.serialize(serializer),
149        }
150    }
151}
152
153impl<'de> Deserialize<'de> for FunctionResultPayload {
154    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155    where
156        D: serde::Deserializer<'de>,
157    {
158        let value = serde_json::Value::deserialize(deserializer)?;
159        Ok(Self::from_value(value))
160    }
161}
162
163impl FunctionResultPayload {
164    /// Classifies a raw JSON value into the appropriate payload variant.
165    ///
166    /// Arrays where every element is a JSON object with a `type` field are
167    /// treated as content-block lists; all other arrays stay as raw JSON.
168    #[must_use]
169    pub fn from_value(value: serde_json::Value) -> Self {
170        match value {
171            serde_json::Value::String(s) => Self::Text(s),
172            serde_json::Value::Array(items) => {
173                let looks_like_contents = !items.is_empty()
174                    && items
175                        .iter()
176                        .all(|v| v.is_object() && v.get("type").is_some_and(|t| t.is_string()));
177                if looks_like_contents {
178                    match serde_json::from_value::<Vec<Content>>(serde_json::Value::Array(
179                        items.clone(),
180                    )) {
181                        Ok(contents) => Self::Contents(contents),
182                        Err(_) => Self::Json(serde_json::Value::Array(items)),
183                    }
184                } else {
185                    Self::Json(serde_json::Value::Array(items))
186                }
187            }
188            other => Self::Json(other),
189        }
190    }
191}
192
193// =============================================================================
194// Step
195// =============================================================================
196
197/// A single step in an interaction (API revision 2026-05-20).
198///
199/// The `steps` array on [`InteractionResponse`](crate::InteractionResponse)
200/// replaces the launch-era `outputs` array. Steps also form the canonical
201/// representation of conversation history when sending stateless multi-turn
202/// requests (`input: [Step, ...]`).
203///
204/// # Forward Compatibility
205///
206/// This enum is `#[non_exhaustive]`. Unrecognized step types deserialize into
207/// [`Step::Unknown`] with the full JSON preserved for roundtrip.
208///
209/// # Example
210///
211/// ```no_run
212/// # use genai_rs::{InteractionResponse, Step};
213/// # let response: InteractionResponse = todo!();
214/// for step in &response.steps {
215///     match step {
216///         Step::ModelOutput { content, .. } => println!("{} content blocks", content.len()),
217///         Step::FunctionCall { name, .. } => println!("Call function: {}", name),
218///         Step::Unknown { step_type, .. } => println!("Unknown step: {}", step_type),
219///         _ => {}
220///     }
221/// }
222/// ```
223#[derive(Clone, Debug, PartialEq)]
224#[non_exhaustive]
225pub enum Step {
226    /// Input provided by the user (`type: "user_input"`).
227    UserInput {
228        /// Content blocks that make up the user input.
229        content: Vec<Content>,
230    },
231    /// Output generated by the model (`type: "model_output"`).
232    ModelOutput {
233        /// Content blocks generated by the model.
234        content: Vec<Content>,
235        /// Error details, if the model output failed.
236        error: Option<StepError>,
237    },
238    /// Internal reasoning (`type: "thought"`).
239    Thought {
240        /// Opaque signature validating the reasoning process. Pass it back
241        /// unchanged when replaying history statelessly.
242        signature: Option<String>,
243        /// Optional human-readable summary of the reasoning (text/image
244        /// content blocks).
245        summary: Vec<Content>,
246    },
247    /// A client-side function call requested by the model
248    /// (`type: "function_call"`).
249    FunctionCall {
250        /// Unique ID for this specific tool call.
251        id: String,
252        /// Name of the function to call.
253        name: String,
254        /// Arguments to pass to the function (JSON object).
255        arguments: serde_json::Value,
256        /// Opaque signature for backend validation. Returned by the API and
257        /// required when replaying this step in stateless history (verified
258        /// live 2026-07; the generated SDK bindings omit it).
259        signature: Option<String>,
260    },
261    /// The result of a client-side function call
262    /// (`type: "function_result"`).
263    FunctionResult {
264        /// The `id` of the [`Step::FunctionCall`] this responds to.
265        call_id: String,
266        /// Function name (optional per spec).
267        name: Option<String>,
268        /// The result: string, JSON, or content blocks.
269        result: FunctionResultPayload,
270        /// Whether the function execution errored.
271        is_error: Option<bool>,
272        /// Opaque signature hash for backend validation; pass through
273        /// unchanged when replaying history.
274        signature: Option<String>,
275    },
276    /// Server-side code execution call (`type: "code_execution_call"`).
277    ///
278    /// Wire format nests `language`/`code` inside an `arguments` object.
279    CodeExecutionCall {
280        /// Unique ID for this call.
281        id: String,
282        /// Programming language (currently only Python).
283        language: CodeExecutionLanguage,
284        /// Source code to execute.
285        code: String,
286        /// Opaque signature; pass through unchanged when replaying history.
287        signature: Option<String>,
288    },
289    /// Server-side code execution result (`type: "code_execution_result"`).
290    CodeExecutionResult {
291        /// The `id` of the corresponding call.
292        call_id: String,
293        /// Output (stdout on success, error message on failure).
294        result: String,
295        /// Whether execution errored.
296        is_error: bool,
297        /// Opaque signature; pass through unchanged.
298        signature: Option<String>,
299    },
300    /// URL context fetch call (`type: "url_context_call"`).
301    UrlContextCall {
302        /// Unique ID for this call.
303        id: String,
304        /// URLs to fetch (wire: nested under `arguments.urls`).
305        urls: Vec<String>,
306        /// Opaque signature; pass through unchanged.
307        signature: Option<String>,
308    },
309    /// URL context fetch result (`type: "url_context_result"`).
310    UrlContextResult {
311        /// The `id` of the corresponding call.
312        call_id: String,
313        /// Per-URL retrieval results.
314        result: Vec<UrlContextResultItem>,
315        /// Whether the fetch errored.
316        is_error: Option<bool>,
317        /// Opaque signature; pass through unchanged.
318        signature: Option<String>,
319    },
320    /// Google Search call (`type: "google_search_call"`).
321    GoogleSearchCall {
322        /// Unique ID for this call.
323        id: String,
324        /// Search queries (wire: nested under `arguments.queries`).
325        queries: Vec<String>,
326        /// Which search backend was used (e.g. `web_search`).
327        search_type: Option<crate::tools::SearchType>,
328        /// Opaque signature; pass through unchanged.
329        signature: Option<String>,
330    },
331    /// Google Search result (`type: "google_search_result"`).
332    GoogleSearchResult {
333        /// The `id` of the corresponding call.
334        call_id: String,
335        /// Search results with source information.
336        result: Vec<GoogleSearchResultItem>,
337        /// Whether the search errored.
338        is_error: Option<bool>,
339        /// Opaque signature; pass through unchanged.
340        signature: Option<String>,
341    },
342    /// MCP server tool call (`type: "mcp_server_tool_call"`).
343    McpServerToolCall {
344        /// Unique ID for this call.
345        id: String,
346        /// Tool name on the MCP server.
347        name: String,
348        /// Name of the MCP server.
349        server_name: String,
350        /// Arguments passed to the tool.
351        arguments: serde_json::Value,
352    },
353    /// MCP server tool result (`type: "mcp_server_tool_result"`).
354    McpServerToolResult {
355        /// The `id` of the corresponding call.
356        call_id: String,
357        /// Tool name (optional per spec).
358        name: Option<String>,
359        /// Server name (optional per spec).
360        server_name: Option<String>,
361        /// The result: string, JSON, or content blocks.
362        result: FunctionResultPayload,
363    },
364    /// File Search call (`type: "file_search_call"`).
365    FileSearchCall {
366        /// Unique ID for this call.
367        id: String,
368        /// Opaque signature; pass through unchanged.
369        signature: Option<String>,
370    },
371    /// File Search result (`type: "file_search_result"`).
372    FileSearchResult {
373        /// The `id` of the corresponding call.
374        call_id: String,
375        /// Retrieved chunks. Note: the 2026-05-20 spec does not document a
376        /// `result` field on this step; it is kept here (deserialized when
377        /// present) because earlier revisions returned it.
378        result: Vec<FileSearchResultItem>,
379        /// Opaque signature; pass through unchanged.
380        signature: Option<String>,
381    },
382    /// Google Maps call (`type: "google_maps_call"`).
383    GoogleMapsCall {
384        /// Unique ID for this call.
385        id: String,
386        /// Location queries (wire: nested under `arguments.queries`).
387        queries: Vec<String>,
388        /// Opaque signature; pass through unchanged.
389        signature: Option<String>,
390    },
391    /// Google Maps result (`type: "google_maps_result"`).
392    GoogleMapsResult {
393        /// The `id` of the corresponding call.
394        call_id: String,
395        /// Places and widget data.
396        result: Vec<GoogleMapsResultItem>,
397        /// Opaque signature; pass through unchanged.
398        signature: Option<String>,
399    },
400    /// Unknown step type for forward compatibility.
401    ///
402    /// Captures step types this library doesn't recognize yet. Roundtrips
403    /// losslessly: serializing an `Unknown` step reproduces the original
404    /// fields with `step_type` as the `"type"` tag.
405    Unknown {
406        /// The unrecognized type name from the API.
407        step_type: String,
408        /// The full JSON data for this step, preserved for debugging.
409        data: serde_json::Value,
410    },
411}
412
413impl Step {
414    // =========================================================================
415    // Unknown helpers (Evergreen pattern)
416    // =========================================================================
417
418    /// Check if this is an unknown step type.
419    #[must_use]
420    pub const fn is_unknown(&self) -> bool {
421        matches!(self, Self::Unknown { .. })
422    }
423
424    /// Returns the step type name if this is an unknown step.
425    #[must_use]
426    pub fn unknown_step_type(&self) -> Option<&str> {
427        match self {
428            Self::Unknown { step_type, .. } => Some(step_type),
429            _ => None,
430        }
431    }
432
433    /// Returns the raw JSON data if this is an unknown step.
434    #[must_use]
435    pub fn unknown_data(&self) -> Option<&serde_json::Value> {
436        match self {
437            Self::Unknown { data, .. } => Some(data),
438            _ => None,
439        }
440    }
441
442    // =========================================================================
443    // Constructors
444    // =========================================================================
445
446    /// Creates a `user_input` step from a plain text message.
447    pub fn user_text(text: impl Into<String>) -> Self {
448        Self::UserInput {
449            content: vec![Content::text(text)],
450        }
451    }
452
453    /// Creates a `user_input` step from content blocks.
454    #[must_use]
455    pub fn user_input(content: Vec<Content>) -> Self {
456        Self::UserInput { content }
457    }
458
459    /// Creates a `model_output` step from a plain text message.
460    ///
461    /// Useful when constructing conversation history manually.
462    pub fn model_text(text: impl Into<String>) -> Self {
463        Self::ModelOutput {
464            content: vec![Content::text(text)],
465            error: None,
466        }
467    }
468
469    /// Creates a `model_output` step from content blocks.
470    #[must_use]
471    pub fn model_output(content: Vec<Content>) -> Self {
472        Self::ModelOutput {
473            content,
474            error: None,
475        }
476    }
477
478    /// Creates a `thought` step carrying only a signature.
479    pub fn thought(signature: impl Into<String>) -> Self {
480        Self::Thought {
481            signature: Some(signature.into()),
482            summary: Vec::new(),
483        }
484    }
485
486    /// Creates a `function_call` step.
487    pub fn function_call(
488        id: impl Into<String>,
489        name: impl Into<String>,
490        arguments: serde_json::Value,
491    ) -> Self {
492        Self::FunctionCall {
493            id: id.into(),
494            name: name.into(),
495            arguments,
496            signature: None,
497        }
498    }
499
500    /// Creates a `function_result` step for a successful execution.
501    ///
502    /// The result can be a string, a JSON value, or content blocks — anything
503    /// convertible into [`FunctionResultPayload`].
504    pub fn function_result(
505        name: impl Into<String>,
506        call_id: impl Into<String>,
507        result: impl Into<FunctionResultPayload>,
508    ) -> Self {
509        Self::FunctionResult {
510            call_id: call_id.into(),
511            name: Some(name.into()),
512            result: result.into(),
513            is_error: None,
514            signature: None,
515        }
516    }
517
518    /// Creates a `function_result` step marked as an error.
519    pub fn function_result_error(
520        name: impl Into<String>,
521        call_id: impl Into<String>,
522        result: impl Into<FunctionResultPayload>,
523    ) -> Self {
524        Self::FunctionResult {
525            call_id: call_id.into(),
526            name: Some(name.into()),
527            result: result.into(),
528            is_error: Some(true),
529            signature: None,
530        }
531    }
532
533    // =========================================================================
534    // Accessors
535    // =========================================================================
536
537    /// Returns the content blocks if this step carries content
538    /// (`user_input` / `model_output`).
539    #[must_use]
540    pub fn content(&self) -> Option<&[Content]> {
541        match self {
542            Self::UserInput { content } | Self::ModelOutput { content, .. } => Some(content),
543            _ => None,
544        }
545    }
546
547    /// Returns the first text block if this step carries text content.
548    #[must_use]
549    pub fn as_text(&self) -> Option<&str> {
550        self.content()?.iter().find_map(Content::as_text)
551    }
552
553    /// Returns the opaque signature carried by this step, if any.
554    ///
555    /// Thought steps, function call/result steps, and built-in tool
556    /// call/result steps carry signatures that must be passed back unchanged
557    /// when replaying history statelessly.
558    #[must_use]
559    pub fn signature(&self) -> Option<&str> {
560        match self {
561            Self::Thought { signature, .. }
562            | Self::FunctionCall { signature, .. }
563            | Self::FunctionResult { signature, .. }
564            | Self::CodeExecutionCall { signature, .. }
565            | Self::CodeExecutionResult { signature, .. }
566            | Self::UrlContextCall { signature, .. }
567            | Self::UrlContextResult { signature, .. }
568            | Self::GoogleSearchCall { signature, .. }
569            | Self::GoogleSearchResult { signature, .. }
570            | Self::FileSearchCall { signature, .. }
571            | Self::FileSearchResult { signature, .. }
572            | Self::GoogleMapsCall { signature, .. }
573            | Self::GoogleMapsResult { signature, .. } => signature.as_deref(),
574            _ => None,
575        }
576    }
577
578    /// Returns the wire `type` tag for this step.
579    #[must_use]
580    pub fn step_type(&self) -> &str {
581        match self {
582            Self::UserInput { .. } => "user_input",
583            Self::ModelOutput { .. } => "model_output",
584            Self::Thought { .. } => "thought",
585            Self::FunctionCall { .. } => "function_call",
586            Self::FunctionResult { .. } => "function_result",
587            Self::CodeExecutionCall { .. } => "code_execution_call",
588            Self::CodeExecutionResult { .. } => "code_execution_result",
589            Self::UrlContextCall { .. } => "url_context_call",
590            Self::UrlContextResult { .. } => "url_context_result",
591            Self::GoogleSearchCall { .. } => "google_search_call",
592            Self::GoogleSearchResult { .. } => "google_search_result",
593            Self::McpServerToolCall { .. } => "mcp_server_tool_call",
594            Self::McpServerToolResult { .. } => "mcp_server_tool_result",
595            Self::FileSearchCall { .. } => "file_search_call",
596            Self::FileSearchResult { .. } => "file_search_result",
597            Self::GoogleMapsCall { .. } => "google_maps_call",
598            Self::GoogleMapsResult { .. } => "google_maps_result",
599            Self::Unknown { step_type, .. } => step_type,
600        }
601    }
602}
603
604impl Serialize for Step {
605    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
606    where
607        S: serde::Serializer,
608    {
609        use serde::ser::SerializeMap;
610
611        let mut map = serializer.serialize_map(None)?;
612        match self {
613            Self::UserInput { content } => {
614                map.serialize_entry("type", "user_input")?;
615                map.serialize_entry("content", content)?;
616            }
617            Self::ModelOutput { content, error } => {
618                map.serialize_entry("type", "model_output")?;
619                map.serialize_entry("content", content)?;
620                if let Some(e) = error {
621                    map.serialize_entry("error", e)?;
622                }
623            }
624            Self::Thought { signature, summary } => {
625                map.serialize_entry("type", "thought")?;
626                if let Some(s) = signature {
627                    map.serialize_entry("signature", s)?;
628                }
629                if !summary.is_empty() {
630                    map.serialize_entry("summary", summary)?;
631                }
632            }
633            Self::FunctionCall {
634                id,
635                name,
636                arguments,
637                signature,
638            } => {
639                map.serialize_entry("type", "function_call")?;
640                map.serialize_entry("id", id)?;
641                map.serialize_entry("name", name)?;
642                map.serialize_entry("arguments", arguments)?;
643                if let Some(s) = signature {
644                    map.serialize_entry("signature", s)?;
645                }
646            }
647            Self::FunctionResult {
648                call_id,
649                name,
650                result,
651                is_error,
652                signature,
653            } => {
654                map.serialize_entry("type", "function_result")?;
655                map.serialize_entry("call_id", call_id)?;
656                if let Some(n) = name {
657                    map.serialize_entry("name", n)?;
658                }
659                map.serialize_entry("result", result)?;
660                if let Some(e) = is_error {
661                    map.serialize_entry("is_error", e)?;
662                }
663                if let Some(s) = signature {
664                    map.serialize_entry("signature", s)?;
665                }
666            }
667            Self::CodeExecutionCall {
668                id,
669                language,
670                code,
671                signature,
672            } => {
673                map.serialize_entry("type", "code_execution_call")?;
674                map.serialize_entry("id", id)?;
675                map.serialize_entry(
676                    "arguments",
677                    &serde_json::json!({ "language": language, "code": code }),
678                )?;
679                if let Some(s) = signature {
680                    map.serialize_entry("signature", s)?;
681                }
682            }
683            Self::CodeExecutionResult {
684                call_id,
685                result,
686                is_error,
687                signature,
688            } => {
689                map.serialize_entry("type", "code_execution_result")?;
690                map.serialize_entry("call_id", call_id)?;
691                map.serialize_entry("result", result)?;
692                map.serialize_entry("is_error", is_error)?;
693                if let Some(s) = signature {
694                    map.serialize_entry("signature", s)?;
695                }
696            }
697            Self::UrlContextCall {
698                id,
699                urls,
700                signature,
701            } => {
702                map.serialize_entry("type", "url_context_call")?;
703                map.serialize_entry("id", id)?;
704                map.serialize_entry("arguments", &serde_json::json!({ "urls": urls }))?;
705                if let Some(s) = signature {
706                    map.serialize_entry("signature", s)?;
707                }
708            }
709            Self::UrlContextResult {
710                call_id,
711                result,
712                is_error,
713                signature,
714            } => {
715                map.serialize_entry("type", "url_context_result")?;
716                map.serialize_entry("call_id", call_id)?;
717                map.serialize_entry("result", result)?;
718                if let Some(e) = is_error {
719                    map.serialize_entry("is_error", e)?;
720                }
721                if let Some(s) = signature {
722                    map.serialize_entry("signature", s)?;
723                }
724            }
725            Self::GoogleSearchCall {
726                id,
727                queries,
728                search_type,
729                signature,
730            } => {
731                map.serialize_entry("type", "google_search_call")?;
732                map.serialize_entry("id", id)?;
733                map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
734                if let Some(st) = search_type {
735                    map.serialize_entry("search_type", st)?;
736                }
737                if let Some(s) = signature {
738                    map.serialize_entry("signature", s)?;
739                }
740            }
741            Self::GoogleSearchResult {
742                call_id,
743                result,
744                is_error,
745                signature,
746            } => {
747                map.serialize_entry("type", "google_search_result")?;
748                map.serialize_entry("call_id", call_id)?;
749                map.serialize_entry("result", result)?;
750                if let Some(e) = is_error {
751                    map.serialize_entry("is_error", e)?;
752                }
753                if let Some(s) = signature {
754                    map.serialize_entry("signature", s)?;
755                }
756            }
757            Self::McpServerToolCall {
758                id,
759                name,
760                server_name,
761                arguments,
762            } => {
763                map.serialize_entry("type", "mcp_server_tool_call")?;
764                map.serialize_entry("id", id)?;
765                map.serialize_entry("name", name)?;
766                map.serialize_entry("server_name", server_name)?;
767                map.serialize_entry("arguments", arguments)?;
768            }
769            Self::McpServerToolResult {
770                call_id,
771                name,
772                server_name,
773                result,
774            } => {
775                map.serialize_entry("type", "mcp_server_tool_result")?;
776                map.serialize_entry("call_id", call_id)?;
777                if let Some(n) = name {
778                    map.serialize_entry("name", n)?;
779                }
780                if let Some(sn) = server_name {
781                    map.serialize_entry("server_name", sn)?;
782                }
783                map.serialize_entry("result", result)?;
784            }
785            Self::FileSearchCall { id, signature } => {
786                map.serialize_entry("type", "file_search_call")?;
787                map.serialize_entry("id", id)?;
788                if let Some(s) = signature {
789                    map.serialize_entry("signature", s)?;
790                }
791            }
792            Self::FileSearchResult {
793                call_id,
794                result,
795                signature,
796            } => {
797                map.serialize_entry("type", "file_search_result")?;
798                map.serialize_entry("call_id", call_id)?;
799                if !result.is_empty() {
800                    map.serialize_entry("result", result)?;
801                }
802                if let Some(s) = signature {
803                    map.serialize_entry("signature", s)?;
804                }
805            }
806            Self::GoogleMapsCall {
807                id,
808                queries,
809                signature,
810            } => {
811                map.serialize_entry("type", "google_maps_call")?;
812                map.serialize_entry("id", id)?;
813                map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
814                if let Some(s) = signature {
815                    map.serialize_entry("signature", s)?;
816                }
817            }
818            Self::GoogleMapsResult {
819                call_id,
820                result,
821                signature,
822            } => {
823                map.serialize_entry("type", "google_maps_result")?;
824                map.serialize_entry("call_id", call_id)?;
825                map.serialize_entry("result", result)?;
826                if let Some(s) = signature {
827                    map.serialize_entry("signature", s)?;
828                }
829            }
830            Self::Unknown { step_type, data } => {
831                map.serialize_entry("type", step_type)?;
832                match data {
833                    serde_json::Value::Object(obj) => {
834                        for (key, value) in obj {
835                            if key != "type" {
836                                map.serialize_entry(key, value)?;
837                            }
838                        }
839                    }
840                    other if !other.is_null() => {
841                        map.serialize_entry("data", other)?;
842                    }
843                    _ => {}
844                }
845            }
846        }
847        map.end()
848    }
849}
850
851/// Extracts a `Vec<String>` from `arguments.<key>` (used by call steps whose
852/// arguments nest a string array).
853fn string_vec_from_arguments(arguments: Option<&serde_json::Value>, key: &str) -> Vec<String> {
854    arguments
855        .and_then(|args| args.get(key))
856        .and_then(|v| v.as_array())
857        .map(|arr| {
858            arr.iter()
859                .filter_map(|v| v.as_str().map(String::from))
860                .collect()
861        })
862        .unwrap_or_default()
863}
864
865impl<'de> Deserialize<'de> for Step {
866    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
867    where
868        D: serde::Deserializer<'de>,
869    {
870        #[cfg(feature = "strict-unknown")]
871        use serde::de::Error as _;
872
873        let value = serde_json::Value::deserialize(deserializer)?;
874
875        #[derive(Deserialize)]
876        #[serde(tag = "type", rename_all = "snake_case")]
877        enum KnownStep {
878            UserInput {
879                #[serde(default)]
880                content: Vec<Content>,
881            },
882            ModelOutput {
883                #[serde(default)]
884                content: Vec<Content>,
885                #[serde(default)]
886                error: Option<StepError>,
887            },
888            Thought {
889                #[serde(default)]
890                signature: Option<String>,
891                #[serde(default)]
892                summary: Vec<Content>,
893            },
894            FunctionCall {
895                id: String,
896                name: String,
897                #[serde(default)]
898                arguments: serde_json::Value,
899                #[serde(default)]
900                signature: Option<String>,
901            },
902            FunctionResult {
903                call_id: String,
904                #[serde(default)]
905                name: Option<String>,
906                #[serde(default)]
907                result: Option<FunctionResultPayload>,
908                #[serde(default)]
909                is_error: Option<bool>,
910                #[serde(default)]
911                signature: Option<String>,
912            },
913            CodeExecutionCall {
914                id: String,
915                #[serde(default)]
916                arguments: Option<serde_json::Value>,
917                #[serde(default)]
918                signature: Option<String>,
919            },
920            CodeExecutionResult {
921                call_id: String,
922                #[serde(default)]
923                result: String,
924                #[serde(default)]
925                is_error: Option<bool>,
926                #[serde(default)]
927                signature: Option<String>,
928            },
929            UrlContextCall {
930                id: String,
931                #[serde(default)]
932                arguments: Option<serde_json::Value>,
933                #[serde(default)]
934                signature: Option<String>,
935            },
936            UrlContextResult {
937                call_id: String,
938                #[serde(default)]
939                result: Vec<UrlContextResultItem>,
940                #[serde(default)]
941                is_error: Option<bool>,
942                #[serde(default)]
943                signature: Option<String>,
944            },
945            GoogleSearchCall {
946                id: String,
947                #[serde(default)]
948                arguments: Option<serde_json::Value>,
949                #[serde(default)]
950                search_type: Option<crate::tools::SearchType>,
951                #[serde(default)]
952                signature: Option<String>,
953            },
954            GoogleSearchResult {
955                call_id: String,
956                #[serde(default)]
957                result: Vec<GoogleSearchResultItem>,
958                #[serde(default)]
959                is_error: Option<bool>,
960                #[serde(default)]
961                signature: Option<String>,
962            },
963            McpServerToolCall {
964                id: String,
965                name: String,
966                server_name: String,
967                #[serde(default)]
968                arguments: serde_json::Value,
969            },
970            McpServerToolResult {
971                call_id: String,
972                #[serde(default)]
973                name: Option<String>,
974                #[serde(default)]
975                server_name: Option<String>,
976                #[serde(default)]
977                result: Option<FunctionResultPayload>,
978            },
979            FileSearchCall {
980                id: String,
981                #[serde(default)]
982                signature: Option<String>,
983            },
984            FileSearchResult {
985                call_id: String,
986                #[serde(default)]
987                result: Vec<FileSearchResultItem>,
988                #[serde(default)]
989                signature: Option<String>,
990            },
991            GoogleMapsCall {
992                id: String,
993                #[serde(default)]
994                arguments: Option<serde_json::Value>,
995                #[serde(default)]
996                signature: Option<String>,
997            },
998            GoogleMapsResult {
999                call_id: String,
1000                #[serde(default)]
1001                result: Vec<GoogleMapsResultItem>,
1002                #[serde(default)]
1003                signature: Option<String>,
1004            },
1005        }
1006
1007        match serde_json::from_value::<KnownStep>(value.clone()) {
1008            Ok(known) => Ok(match known {
1009                KnownStep::UserInput { content } => Step::UserInput { content },
1010                KnownStep::ModelOutput { content, error } => Step::ModelOutput { content, error },
1011                KnownStep::Thought { signature, summary } => Step::Thought { signature, summary },
1012                KnownStep::FunctionCall {
1013                    id,
1014                    name,
1015                    arguments,
1016                    signature,
1017                } => Step::FunctionCall {
1018                    id,
1019                    name,
1020                    arguments,
1021                    signature,
1022                },
1023                KnownStep::FunctionResult {
1024                    call_id,
1025                    name,
1026                    result,
1027                    is_error,
1028                    signature,
1029                } => Step::FunctionResult {
1030                    call_id,
1031                    name,
1032                    result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1033                    is_error,
1034                    signature,
1035                },
1036                KnownStep::CodeExecutionCall {
1037                    id,
1038                    arguments,
1039                    signature,
1040                } => {
1041                    let language = arguments
1042                        .as_ref()
1043                        .and_then(|a| a.get("language"))
1044                        .and_then(|l| {
1045                            serde_json::from_value::<CodeExecutionLanguage>(l.clone()).ok()
1046                        })
1047                        .unwrap_or_default();
1048                    let code = arguments
1049                        .as_ref()
1050                        .and_then(|a| a.get("code"))
1051                        .and_then(|c| c.as_str())
1052                        .unwrap_or_default()
1053                        .to_string();
1054                    Step::CodeExecutionCall {
1055                        id,
1056                        language,
1057                        code,
1058                        signature,
1059                    }
1060                }
1061                KnownStep::CodeExecutionResult {
1062                    call_id,
1063                    result,
1064                    is_error,
1065                    signature,
1066                } => Step::CodeExecutionResult {
1067                    call_id,
1068                    result,
1069                    is_error: is_error.unwrap_or(false),
1070                    signature,
1071                },
1072                KnownStep::UrlContextCall {
1073                    id,
1074                    arguments,
1075                    signature,
1076                } => Step::UrlContextCall {
1077                    id,
1078                    urls: string_vec_from_arguments(arguments.as_ref(), "urls"),
1079                    signature,
1080                },
1081                KnownStep::UrlContextResult {
1082                    call_id,
1083                    result,
1084                    is_error,
1085                    signature,
1086                } => Step::UrlContextResult {
1087                    call_id,
1088                    result,
1089                    is_error,
1090                    signature,
1091                },
1092                KnownStep::GoogleSearchCall {
1093                    id,
1094                    arguments,
1095                    search_type,
1096                    signature,
1097                } => Step::GoogleSearchCall {
1098                    id,
1099                    queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1100                    search_type,
1101                    signature,
1102                },
1103                KnownStep::GoogleSearchResult {
1104                    call_id,
1105                    result,
1106                    is_error,
1107                    signature,
1108                } => Step::GoogleSearchResult {
1109                    call_id,
1110                    result,
1111                    is_error,
1112                    signature,
1113                },
1114                KnownStep::McpServerToolCall {
1115                    id,
1116                    name,
1117                    server_name,
1118                    arguments,
1119                } => Step::McpServerToolCall {
1120                    id,
1121                    name,
1122                    server_name,
1123                    arguments,
1124                },
1125                KnownStep::McpServerToolResult {
1126                    call_id,
1127                    name,
1128                    server_name,
1129                    result,
1130                } => Step::McpServerToolResult {
1131                    call_id,
1132                    name,
1133                    server_name,
1134                    result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1135                },
1136                KnownStep::FileSearchCall { id, signature } => {
1137                    Step::FileSearchCall { id, signature }
1138                }
1139                KnownStep::FileSearchResult {
1140                    call_id,
1141                    result,
1142                    signature,
1143                } => Step::FileSearchResult {
1144                    call_id,
1145                    result,
1146                    signature,
1147                },
1148                KnownStep::GoogleMapsCall {
1149                    id,
1150                    arguments,
1151                    signature,
1152                } => Step::GoogleMapsCall {
1153                    id,
1154                    queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1155                    signature,
1156                },
1157                KnownStep::GoogleMapsResult {
1158                    call_id,
1159                    result,
1160                    signature,
1161                } => Step::GoogleMapsResult {
1162                    call_id,
1163                    result,
1164                    signature,
1165                },
1166            }),
1167            Err(parse_error) => {
1168                let step_type = value
1169                    .get("type")
1170                    .and_then(|v| v.as_str())
1171                    .unwrap_or("<missing type>")
1172                    .to_string();
1173
1174                tracing::warn!(
1175                    "Encountered unknown Step type '{}'. Parse error: {}. \
1176                     This may indicate a new API feature or a malformed response. \
1177                     The step will be preserved in the Unknown variant.",
1178                    step_type,
1179                    parse_error
1180                );
1181
1182                #[cfg(feature = "strict-unknown")]
1183                {
1184                    Err(D::Error::custom(format!(
1185                        "Unknown Step type '{}'. \
1186                         Strict mode is enabled via the 'strict-unknown' feature flag. \
1187                         Either update the library or disable strict mode.",
1188                        step_type
1189                    )))
1190                }
1191
1192                #[cfg(not(feature = "strict-unknown"))]
1193                {
1194                    Ok(Step::Unknown {
1195                        step_type,
1196                        data: value,
1197                    })
1198                }
1199            }
1200        }
1201    }
1202}
1203
1204// =============================================================================
1205// StepDelta (streaming `step.delta` payloads)
1206// =============================================================================
1207
1208/// The payload of a `step.delta` SSE event.
1209///
1210/// Deltas incrementally build the step announced by the matching `step.start`
1211/// event: text arrives in fragments, function-call arguments stream as JSON
1212/// string fragments (`arguments_delta`), thought summaries and signatures
1213/// arrive separately, and built-in tool calls/results are pushed as they
1214/// resolve.
1215///
1216/// # Forward Compatibility
1217///
1218/// `#[non_exhaustive]`; unrecognized delta types deserialize into
1219/// [`StepDelta::Unknown`] with data preserved.
1220#[derive(Clone, Debug, PartialEq)]
1221#[non_exhaustive]
1222pub enum StepDelta {
1223    /// Text fragment (`type: "text"`).
1224    Text {
1225        /// The text fragment.
1226        text: String,
1227    },
1228    /// Image data (`type: "image"`).
1229    Image {
1230        /// Base64 image data.
1231        data: Option<String>,
1232        /// URI reference.
1233        uri: Option<String>,
1234        /// MIME type.
1235        mime_type: Option<String>,
1236        /// Media resolution.
1237        resolution: Option<Resolution>,
1238    },
1239    /// Audio data (`type: "audio"`).
1240    Audio {
1241        /// Base64 audio data.
1242        data: Option<String>,
1243        /// URI reference.
1244        uri: Option<String>,
1245        /// MIME type.
1246        mime_type: Option<String>,
1247        /// Sample rate in Hz (legacy field name).
1248        rate: Option<u32>,
1249        /// Sample rate in Hz.
1250        sample_rate: Option<u32>,
1251        /// Number of audio channels.
1252        channels: Option<u32>,
1253    },
1254    /// Video data (`type: "video"`).
1255    Video {
1256        /// Base64 video data.
1257        data: Option<String>,
1258        /// URI reference.
1259        uri: Option<String>,
1260        /// MIME type.
1261        mime_type: Option<String>,
1262        /// Media resolution.
1263        resolution: Option<Resolution>,
1264    },
1265    /// Document data (`type: "document"`).
1266    Document {
1267        /// Base64 document data.
1268        data: Option<String>,
1269        /// URI reference.
1270        uri: Option<String>,
1271        /// MIME type.
1272        mime_type: Option<String>,
1273    },
1274    /// Thought summary content (`type: "thought_summary"`).
1275    ThoughtSummary {
1276        /// The summary content block (text or image).
1277        content: Option<Content>,
1278    },
1279    /// Thought signature fragment (`type: "thought_signature"`).
1280    ThoughtSignature {
1281        /// The signature fragment.
1282        signature: Option<String>,
1283    },
1284    /// Text annotations (`type: "text_annotation_delta"`).
1285    TextAnnotation {
1286        /// Citation annotations for previously streamed text.
1287        annotations: Vec<Annotation>,
1288    },
1289    /// Function-call arguments fragment (`type: "arguments_delta"`).
1290    ///
1291    /// Arguments for a `function_call` step stream incrementally as raw JSON
1292    /// string fragments; concatenate them and parse when the step stops.
1293    ArgumentsDelta {
1294        /// The raw JSON fragment.
1295        arguments: String,
1296    },
1297    /// Function result (`type: "function_result"`).
1298    FunctionResult {
1299        /// The `id` of the call this result responds to.
1300        call_id: String,
1301        /// Function name.
1302        name: Option<String>,
1303        /// The result payload.
1304        result: FunctionResultPayload,
1305        /// Whether execution errored.
1306        is_error: Option<bool>,
1307    },
1308    /// Code execution call delta (`type: "code_execution_call"`).
1309    CodeExecutionCall {
1310        /// Programming language.
1311        language: Option<CodeExecutionLanguage>,
1312        /// Source code fragment.
1313        code: Option<String>,
1314        /// Opaque signature.
1315        signature: Option<String>,
1316    },
1317    /// Code execution result delta (`type: "code_execution_result"`).
1318    CodeExecutionResult {
1319        /// Execution output.
1320        result: String,
1321        /// Whether execution errored.
1322        is_error: Option<bool>,
1323        /// Opaque signature.
1324        signature: Option<String>,
1325    },
1326    /// URL context call delta (`type: "url_context_call"`).
1327    UrlContextCall {
1328        /// URLs to fetch.
1329        urls: Vec<String>,
1330        /// Opaque signature.
1331        signature: Option<String>,
1332    },
1333    /// URL context result delta (`type: "url_context_result"`).
1334    UrlContextResult {
1335        /// Per-URL retrieval results.
1336        result: Vec<UrlContextResultItem>,
1337        /// Whether the fetch errored.
1338        is_error: Option<bool>,
1339        /// Opaque signature.
1340        signature: Option<String>,
1341    },
1342    /// Google Search call delta (`type: "google_search_call"`).
1343    GoogleSearchCall {
1344        /// Search queries.
1345        queries: Vec<String>,
1346        /// Opaque signature.
1347        signature: Option<String>,
1348    },
1349    /// Google Search result delta (`type: "google_search_result"`).
1350    GoogleSearchResult {
1351        /// Search results.
1352        result: Vec<GoogleSearchResultItem>,
1353        /// Whether the search errored.
1354        is_error: Option<bool>,
1355        /// Opaque signature.
1356        signature: Option<String>,
1357    },
1358    /// MCP server tool call delta (`type: "mcp_server_tool_call"`).
1359    McpServerToolCall {
1360        /// Tool name.
1361        name: String,
1362        /// MCP server name.
1363        server_name: String,
1364        /// Tool arguments.
1365        arguments: serde_json::Value,
1366    },
1367    /// MCP server tool result delta (`type: "mcp_server_tool_result"`).
1368    McpServerToolResult {
1369        /// Tool name.
1370        name: Option<String>,
1371        /// MCP server name.
1372        server_name: Option<String>,
1373        /// The result payload.
1374        result: FunctionResultPayload,
1375    },
1376    /// File search call delta (`type: "file_search_call"`).
1377    FileSearchCall {
1378        /// Opaque signature.
1379        signature: Option<String>,
1380    },
1381    /// File search result delta (`type: "file_search_result"`).
1382    FileSearchResult {
1383        /// Retrieved chunks.
1384        result: Vec<FileSearchResultItem>,
1385        /// Opaque signature.
1386        signature: Option<String>,
1387    },
1388    /// Google Maps call delta (`type: "google_maps_call"`).
1389    GoogleMapsCall {
1390        /// Location queries.
1391        queries: Vec<String>,
1392        /// Opaque signature.
1393        signature: Option<String>,
1394    },
1395    /// Google Maps result delta (`type: "google_maps_result"`).
1396    GoogleMapsResult {
1397        /// Places and widget data.
1398        result: Vec<GoogleMapsResultItem>,
1399        /// Opaque signature.
1400        signature: Option<String>,
1401    },
1402    /// Unknown delta type for forward compatibility.
1403    Unknown {
1404        /// The unrecognized type name from the API.
1405        delta_type: String,
1406        /// The full JSON data, preserved for debugging and roundtrip.
1407        data: serde_json::Value,
1408    },
1409}
1410
1411impl StepDelta {
1412    /// Check if this is an unknown delta type.
1413    #[must_use]
1414    pub const fn is_unknown(&self) -> bool {
1415        matches!(self, Self::Unknown { .. })
1416    }
1417
1418    /// Returns the delta type name if this is an unknown delta.
1419    #[must_use]
1420    pub fn unknown_delta_type(&self) -> Option<&str> {
1421        match self {
1422            Self::Unknown { delta_type, .. } => Some(delta_type),
1423            _ => None,
1424        }
1425    }
1426
1427    /// Returns the raw JSON data if this is an unknown delta.
1428    #[must_use]
1429    pub fn unknown_data(&self) -> Option<&serde_json::Value> {
1430        match self {
1431            Self::Unknown { data, .. } => Some(data),
1432            _ => None,
1433        }
1434    }
1435
1436    /// Returns the text fragment if this is a `Text` delta.
1437    #[must_use]
1438    pub fn as_text(&self) -> Option<&str> {
1439        match self {
1440            Self::Text { text } => Some(text),
1441            _ => None,
1442        }
1443    }
1444
1445    /// Returns the raw arguments fragment if this is an `ArgumentsDelta`.
1446    #[must_use]
1447    pub fn as_arguments_delta(&self) -> Option<&str> {
1448        match self {
1449            Self::ArgumentsDelta { arguments } => Some(arguments),
1450            _ => None,
1451        }
1452    }
1453}
1454
1455impl Serialize for StepDelta {
1456    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1457    where
1458        S: serde::Serializer,
1459    {
1460        use serde::ser::SerializeMap;
1461
1462        /// Serialize an optional entry only when present.
1463        macro_rules! opt_entry {
1464            ($map:expr, $key:literal, $val:expr) => {
1465                if let Some(v) = $val {
1466                    $map.serialize_entry($key, v)?;
1467                }
1468            };
1469        }
1470
1471        let mut map = serializer.serialize_map(None)?;
1472        match self {
1473            Self::Text { text } => {
1474                map.serialize_entry("type", "text")?;
1475                map.serialize_entry("text", text)?;
1476            }
1477            Self::Image {
1478                data,
1479                uri,
1480                mime_type,
1481                resolution,
1482            } => {
1483                map.serialize_entry("type", "image")?;
1484                opt_entry!(map, "data", data);
1485                opt_entry!(map, "uri", uri);
1486                opt_entry!(map, "mime_type", mime_type);
1487                opt_entry!(map, "resolution", resolution);
1488            }
1489            Self::Audio {
1490                data,
1491                uri,
1492                mime_type,
1493                rate,
1494                sample_rate,
1495                channels,
1496            } => {
1497                map.serialize_entry("type", "audio")?;
1498                opt_entry!(map, "data", data);
1499                opt_entry!(map, "uri", uri);
1500                opt_entry!(map, "mime_type", mime_type);
1501                opt_entry!(map, "rate", rate);
1502                opt_entry!(map, "sample_rate", sample_rate);
1503                opt_entry!(map, "channels", channels);
1504            }
1505            Self::Video {
1506                data,
1507                uri,
1508                mime_type,
1509                resolution,
1510            } => {
1511                map.serialize_entry("type", "video")?;
1512                opt_entry!(map, "data", data);
1513                opt_entry!(map, "uri", uri);
1514                opt_entry!(map, "mime_type", mime_type);
1515                opt_entry!(map, "resolution", resolution);
1516            }
1517            Self::Document {
1518                data,
1519                uri,
1520                mime_type,
1521            } => {
1522                map.serialize_entry("type", "document")?;
1523                opt_entry!(map, "data", data);
1524                opt_entry!(map, "uri", uri);
1525                opt_entry!(map, "mime_type", mime_type);
1526            }
1527            Self::ThoughtSummary { content } => {
1528                map.serialize_entry("type", "thought_summary")?;
1529                opt_entry!(map, "content", content);
1530            }
1531            Self::ThoughtSignature { signature } => {
1532                map.serialize_entry("type", "thought_signature")?;
1533                opt_entry!(map, "signature", signature);
1534            }
1535            Self::TextAnnotation { annotations } => {
1536                map.serialize_entry("type", "text_annotation_delta")?;
1537                map.serialize_entry("annotations", annotations)?;
1538            }
1539            Self::ArgumentsDelta { arguments } => {
1540                map.serialize_entry("type", "arguments_delta")?;
1541                map.serialize_entry("arguments", arguments)?;
1542            }
1543            Self::FunctionResult {
1544                call_id,
1545                name,
1546                result,
1547                is_error,
1548            } => {
1549                map.serialize_entry("type", "function_result")?;
1550                map.serialize_entry("call_id", call_id)?;
1551                opt_entry!(map, "name", name);
1552                map.serialize_entry("result", result)?;
1553                opt_entry!(map, "is_error", is_error);
1554            }
1555            Self::CodeExecutionCall {
1556                language,
1557                code,
1558                signature,
1559            } => {
1560                map.serialize_entry("type", "code_execution_call")?;
1561                map.serialize_entry(
1562                    "arguments",
1563                    &serde_json::json!({ "language": language, "code": code }),
1564                )?;
1565                opt_entry!(map, "signature", signature);
1566            }
1567            Self::CodeExecutionResult {
1568                result,
1569                is_error,
1570                signature,
1571            } => {
1572                map.serialize_entry("type", "code_execution_result")?;
1573                map.serialize_entry("result", result)?;
1574                opt_entry!(map, "is_error", is_error);
1575                opt_entry!(map, "signature", signature);
1576            }
1577            Self::UrlContextCall { urls, signature } => {
1578                map.serialize_entry("type", "url_context_call")?;
1579                map.serialize_entry("arguments", &serde_json::json!({ "urls": urls }))?;
1580                opt_entry!(map, "signature", signature);
1581            }
1582            Self::UrlContextResult {
1583                result,
1584                is_error,
1585                signature,
1586            } => {
1587                map.serialize_entry("type", "url_context_result")?;
1588                map.serialize_entry("result", result)?;
1589                opt_entry!(map, "is_error", is_error);
1590                opt_entry!(map, "signature", signature);
1591            }
1592            Self::GoogleSearchCall { queries, signature } => {
1593                map.serialize_entry("type", "google_search_call")?;
1594                map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
1595                opt_entry!(map, "signature", signature);
1596            }
1597            Self::GoogleSearchResult {
1598                result,
1599                is_error,
1600                signature,
1601            } => {
1602                map.serialize_entry("type", "google_search_result")?;
1603                map.serialize_entry("result", result)?;
1604                opt_entry!(map, "is_error", is_error);
1605                opt_entry!(map, "signature", signature);
1606            }
1607            Self::McpServerToolCall {
1608                name,
1609                server_name,
1610                arguments,
1611            } => {
1612                map.serialize_entry("type", "mcp_server_tool_call")?;
1613                map.serialize_entry("name", name)?;
1614                map.serialize_entry("server_name", server_name)?;
1615                map.serialize_entry("arguments", arguments)?;
1616            }
1617            Self::McpServerToolResult {
1618                name,
1619                server_name,
1620                result,
1621            } => {
1622                map.serialize_entry("type", "mcp_server_tool_result")?;
1623                opt_entry!(map, "name", name);
1624                opt_entry!(map, "server_name", server_name);
1625                map.serialize_entry("result", result)?;
1626            }
1627            Self::FileSearchCall { signature } => {
1628                map.serialize_entry("type", "file_search_call")?;
1629                opt_entry!(map, "signature", signature);
1630            }
1631            Self::FileSearchResult { result, signature } => {
1632                map.serialize_entry("type", "file_search_result")?;
1633                map.serialize_entry("result", result)?;
1634                opt_entry!(map, "signature", signature);
1635            }
1636            Self::GoogleMapsCall { queries, signature } => {
1637                map.serialize_entry("type", "google_maps_call")?;
1638                map.serialize_entry("arguments", &serde_json::json!({ "queries": queries }))?;
1639                opt_entry!(map, "signature", signature);
1640            }
1641            Self::GoogleMapsResult { result, signature } => {
1642                map.serialize_entry("type", "google_maps_result")?;
1643                map.serialize_entry("result", result)?;
1644                opt_entry!(map, "signature", signature);
1645            }
1646            Self::Unknown { delta_type, data } => {
1647                map.serialize_entry("type", delta_type)?;
1648                match data {
1649                    serde_json::Value::Object(obj) => {
1650                        for (key, value) in obj {
1651                            if key != "type" {
1652                                map.serialize_entry(key, value)?;
1653                            }
1654                        }
1655                    }
1656                    other if !other.is_null() => {
1657                        map.serialize_entry("data", other)?;
1658                    }
1659                    _ => {}
1660                }
1661            }
1662        }
1663        map.end()
1664    }
1665}
1666
1667impl<'de> Deserialize<'de> for StepDelta {
1668    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1669    where
1670        D: serde::Deserializer<'de>,
1671    {
1672        let value = serde_json::Value::deserialize(deserializer)?;
1673
1674        #[derive(Deserialize)]
1675        #[serde(tag = "type", rename_all = "snake_case")]
1676        enum KnownDelta {
1677            Text {
1678                #[serde(default)]
1679                text: String,
1680            },
1681            Image {
1682                #[serde(default)]
1683                data: Option<String>,
1684                #[serde(default)]
1685                uri: Option<String>,
1686                #[serde(default)]
1687                mime_type: Option<String>,
1688                #[serde(default)]
1689                resolution: Option<Resolution>,
1690            },
1691            Audio {
1692                #[serde(default)]
1693                data: Option<String>,
1694                #[serde(default)]
1695                uri: Option<String>,
1696                #[serde(default)]
1697                mime_type: Option<String>,
1698                #[serde(default)]
1699                rate: Option<u32>,
1700                #[serde(default)]
1701                sample_rate: Option<u32>,
1702                #[serde(default)]
1703                channels: Option<u32>,
1704            },
1705            Video {
1706                #[serde(default)]
1707                data: Option<String>,
1708                #[serde(default)]
1709                uri: Option<String>,
1710                #[serde(default)]
1711                mime_type: Option<String>,
1712                #[serde(default)]
1713                resolution: Option<Resolution>,
1714            },
1715            Document {
1716                #[serde(default)]
1717                data: Option<String>,
1718                #[serde(default)]
1719                uri: Option<String>,
1720                #[serde(default)]
1721                mime_type: Option<String>,
1722            },
1723            ThoughtSummary {
1724                #[serde(default)]
1725                content: Option<Content>,
1726            },
1727            ThoughtSignature {
1728                #[serde(default)]
1729                signature: Option<String>,
1730            },
1731            #[serde(rename = "text_annotation_delta")]
1732            TextAnnotation {
1733                #[serde(default)]
1734                annotations: Vec<Annotation>,
1735            },
1736            ArgumentsDelta {
1737                #[serde(default)]
1738                arguments: String,
1739            },
1740            FunctionResult {
1741                call_id: String,
1742                #[serde(default)]
1743                name: Option<String>,
1744                #[serde(default)]
1745                result: Option<FunctionResultPayload>,
1746                #[serde(default)]
1747                is_error: Option<bool>,
1748            },
1749            CodeExecutionCall {
1750                #[serde(default)]
1751                arguments: Option<serde_json::Value>,
1752                #[serde(default)]
1753                signature: Option<String>,
1754            },
1755            CodeExecutionResult {
1756                #[serde(default)]
1757                result: String,
1758                #[serde(default)]
1759                is_error: Option<bool>,
1760                #[serde(default)]
1761                signature: Option<String>,
1762            },
1763            UrlContextCall {
1764                #[serde(default)]
1765                arguments: Option<serde_json::Value>,
1766                #[serde(default)]
1767                signature: Option<String>,
1768            },
1769            UrlContextResult {
1770                #[serde(default)]
1771                result: Vec<UrlContextResultItem>,
1772                #[serde(default)]
1773                is_error: Option<bool>,
1774                #[serde(default)]
1775                signature: Option<String>,
1776            },
1777            GoogleSearchCall {
1778                #[serde(default)]
1779                arguments: Option<serde_json::Value>,
1780                #[serde(default)]
1781                signature: Option<String>,
1782            },
1783            GoogleSearchResult {
1784                #[serde(default)]
1785                result: Vec<GoogleSearchResultItem>,
1786                #[serde(default)]
1787                is_error: Option<bool>,
1788                #[serde(default)]
1789                signature: Option<String>,
1790            },
1791            McpServerToolCall {
1792                name: String,
1793                server_name: String,
1794                #[serde(default)]
1795                arguments: serde_json::Value,
1796            },
1797            McpServerToolResult {
1798                #[serde(default)]
1799                name: Option<String>,
1800                #[serde(default)]
1801                server_name: Option<String>,
1802                #[serde(default)]
1803                result: Option<FunctionResultPayload>,
1804            },
1805            FileSearchCall {
1806                #[serde(default)]
1807                signature: Option<String>,
1808            },
1809            FileSearchResult {
1810                #[serde(default)]
1811                result: Vec<FileSearchResultItem>,
1812                #[serde(default)]
1813                signature: Option<String>,
1814            },
1815            GoogleMapsCall {
1816                #[serde(default)]
1817                arguments: Option<serde_json::Value>,
1818                #[serde(default)]
1819                signature: Option<String>,
1820            },
1821            GoogleMapsResult {
1822                #[serde(default)]
1823                result: Vec<GoogleMapsResultItem>,
1824                #[serde(default)]
1825                signature: Option<String>,
1826            },
1827        }
1828
1829        match serde_json::from_value::<KnownDelta>(value.clone()) {
1830            Ok(known) => Ok(match known {
1831                KnownDelta::Text { text } => StepDelta::Text { text },
1832                KnownDelta::Image {
1833                    data,
1834                    uri,
1835                    mime_type,
1836                    resolution,
1837                } => StepDelta::Image {
1838                    data,
1839                    uri,
1840                    mime_type,
1841                    resolution,
1842                },
1843                KnownDelta::Audio {
1844                    data,
1845                    uri,
1846                    mime_type,
1847                    rate,
1848                    sample_rate,
1849                    channels,
1850                } => StepDelta::Audio {
1851                    data,
1852                    uri,
1853                    mime_type,
1854                    rate,
1855                    sample_rate,
1856                    channels,
1857                },
1858                KnownDelta::Video {
1859                    data,
1860                    uri,
1861                    mime_type,
1862                    resolution,
1863                } => StepDelta::Video {
1864                    data,
1865                    uri,
1866                    mime_type,
1867                    resolution,
1868                },
1869                KnownDelta::Document {
1870                    data,
1871                    uri,
1872                    mime_type,
1873                } => StepDelta::Document {
1874                    data,
1875                    uri,
1876                    mime_type,
1877                },
1878                KnownDelta::ThoughtSummary { content } => StepDelta::ThoughtSummary { content },
1879                KnownDelta::ThoughtSignature { signature } => {
1880                    StepDelta::ThoughtSignature { signature }
1881                }
1882                KnownDelta::TextAnnotation { annotations } => {
1883                    StepDelta::TextAnnotation { annotations }
1884                }
1885                KnownDelta::ArgumentsDelta { arguments } => StepDelta::ArgumentsDelta { arguments },
1886                KnownDelta::FunctionResult {
1887                    call_id,
1888                    name,
1889                    result,
1890                    is_error,
1891                } => StepDelta::FunctionResult {
1892                    call_id,
1893                    name,
1894                    result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1895                    is_error,
1896                },
1897                KnownDelta::CodeExecutionCall {
1898                    arguments,
1899                    signature,
1900                } => {
1901                    StepDelta::CodeExecutionCall {
1902                        language: arguments.as_ref().and_then(|a| a.get("language")).and_then(
1903                            |l| serde_json::from_value::<CodeExecutionLanguage>(l.clone()).ok(),
1904                        ),
1905                        code: arguments
1906                            .as_ref()
1907                            .and_then(|a| a.get("code"))
1908                            .and_then(|c| c.as_str())
1909                            .map(String::from),
1910                        signature,
1911                    }
1912                }
1913                KnownDelta::CodeExecutionResult {
1914                    result,
1915                    is_error,
1916                    signature,
1917                } => StepDelta::CodeExecutionResult {
1918                    result,
1919                    is_error,
1920                    signature,
1921                },
1922                KnownDelta::UrlContextCall {
1923                    arguments,
1924                    signature,
1925                } => StepDelta::UrlContextCall {
1926                    urls: string_vec_from_arguments(arguments.as_ref(), "urls"),
1927                    signature,
1928                },
1929                KnownDelta::UrlContextResult {
1930                    result,
1931                    is_error,
1932                    signature,
1933                } => StepDelta::UrlContextResult {
1934                    result,
1935                    is_error,
1936                    signature,
1937                },
1938                KnownDelta::GoogleSearchCall {
1939                    arguments,
1940                    signature,
1941                } => StepDelta::GoogleSearchCall {
1942                    queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1943                    signature,
1944                },
1945                KnownDelta::GoogleSearchResult {
1946                    result,
1947                    is_error,
1948                    signature,
1949                } => StepDelta::GoogleSearchResult {
1950                    result,
1951                    is_error,
1952                    signature,
1953                },
1954                KnownDelta::McpServerToolCall {
1955                    name,
1956                    server_name,
1957                    arguments,
1958                } => StepDelta::McpServerToolCall {
1959                    name,
1960                    server_name,
1961                    arguments,
1962                },
1963                KnownDelta::McpServerToolResult {
1964                    name,
1965                    server_name,
1966                    result,
1967                } => StepDelta::McpServerToolResult {
1968                    name,
1969                    server_name,
1970                    result: result.unwrap_or(FunctionResultPayload::Json(serde_json::Value::Null)),
1971                },
1972                KnownDelta::FileSearchCall { signature } => StepDelta::FileSearchCall { signature },
1973                KnownDelta::FileSearchResult { result, signature } => {
1974                    StepDelta::FileSearchResult { result, signature }
1975                }
1976                KnownDelta::GoogleMapsCall {
1977                    arguments,
1978                    signature,
1979                } => StepDelta::GoogleMapsCall {
1980                    queries: string_vec_from_arguments(arguments.as_ref(), "queries"),
1981                    signature,
1982                },
1983                KnownDelta::GoogleMapsResult { result, signature } => {
1984                    StepDelta::GoogleMapsResult { result, signature }
1985                }
1986            }),
1987            Err(parse_error) => {
1988                let delta_type = value
1989                    .get("type")
1990                    .and_then(|v| v.as_str())
1991                    .unwrap_or("<missing type>")
1992                    .to_string();
1993
1994                tracing::warn!(
1995                    "Encountered unknown StepDelta type '{}'. Parse error: {}. \
1996                     The delta will be preserved in the Unknown variant.",
1997                    delta_type,
1998                    parse_error
1999                );
2000
2001                Ok(StepDelta::Unknown {
2002                    delta_type,
2003                    data: value,
2004                })
2005            }
2006        }
2007    }
2008}
2009
2010// =============================================================================
2011// Streaming step accumulation
2012// =============================================================================
2013
2014/// Accumulates `step.start` / `step.delta` / `step.stop` events into complete
2015/// [`Step`]s, so streaming consumers get a fully-populated `steps` array on
2016/// the final response even when the server's `interaction.completed` payload
2017/// omits it.
2018#[derive(Debug, Default)]
2019pub(crate) struct StepAccumulator {
2020    steps: std::collections::BTreeMap<usize, AccumulatedStep>,
2021    /// Last cumulative interaction usage reported on a `step.stop` event.
2022    /// Used as a fallback when the terminal event carries no usage.
2023    last_cumulative_usage: Option<crate::response::UsageMetadata>,
2024}
2025
2026#[derive(Debug)]
2027struct AccumulatedStep {
2028    step: Step,
2029    /// Raw buffer for `arguments_delta` fragments (function calls).
2030    args_buffer: String,
2031}
2032
2033impl StepAccumulator {
2034    pub(crate) fn new() -> Self {
2035        Self::default()
2036    }
2037
2038    pub(crate) fn is_empty(&self) -> bool {
2039        self.steps.is_empty()
2040    }
2041
2042    /// Records a `step.start` event.
2043    pub(crate) fn start(&mut self, index: usize, step: Step) {
2044        self.steps.insert(
2045            index,
2046            AccumulatedStep {
2047                step,
2048                args_buffer: String::new(),
2049            },
2050        );
2051    }
2052
2053    /// Applies a `step.delta` event to the step at `index`.
2054    pub(crate) fn apply_delta(&mut self, index: usize, delta: &StepDelta) {
2055        let entry = self.steps.entry(index).or_insert_with(|| AccumulatedStep {
2056            // Deltas without a preceding step.start most commonly belong to a
2057            // model_output step; start one so the content is not dropped.
2058            step: Step::ModelOutput {
2059                content: Vec::new(),
2060                error: None,
2061            },
2062            args_buffer: String::new(),
2063        });
2064
2065        match delta {
2066            StepDelta::Text { text } => {
2067                if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2068                    &mut entry.step
2069                {
2070                    if let Some(Content::Text { text: Some(t), .. }) = content.last_mut() {
2071                        t.push_str(text);
2072                    } else {
2073                        content.push(Content::text(text.clone()));
2074                    }
2075                }
2076            }
2077            StepDelta::TextAnnotation { annotations } => {
2078                if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2079                    &mut entry.step
2080                    && let Some(Content::Text {
2081                        annotations: annots,
2082                        ..
2083                    }) = content.last_mut()
2084                {
2085                    annots
2086                        .get_or_insert_with(Vec::new)
2087                        .extend(annotations.iter().cloned());
2088                }
2089            }
2090            StepDelta::Image {
2091                data,
2092                uri,
2093                mime_type,
2094                resolution,
2095            } => {
2096                if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2097                    &mut entry.step
2098                {
2099                    // Images may stream in multiple chunks; append base64 data
2100                    // to the previous image block when present.
2101                    if let (
2102                        Some(Content::Image {
2103                            data: Some(existing),
2104                            ..
2105                        }),
2106                        Some(new_data),
2107                    ) = (content.last_mut(), data.as_ref())
2108                    {
2109                        existing.push_str(new_data);
2110                    } else {
2111                        content.push(Content::Image {
2112                            data: data.clone(),
2113                            uri: uri.clone(),
2114                            mime_type: mime_type.clone(),
2115                            resolution: resolution.clone(),
2116                        });
2117                    }
2118                }
2119            }
2120            StepDelta::Audio {
2121                data,
2122                uri,
2123                mime_type,
2124                rate,
2125                sample_rate,
2126                channels,
2127            } => {
2128                if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2129                    &mut entry.step
2130                {
2131                    // Audio may stream in multiple chunks; append base64 data to
2132                    // the previous audio block when present.
2133                    if let (
2134                        Some(Content::Audio {
2135                            data: Some(existing),
2136                            ..
2137                        }),
2138                        Some(new_data),
2139                    ) = (content.last_mut(), data.as_ref())
2140                    {
2141                        existing.push_str(new_data);
2142                    } else {
2143                        content.push(Content::Audio {
2144                            data: data.clone(),
2145                            uri: uri.clone(),
2146                            mime_type: mime_type.clone(),
2147                            sample_rate: sample_rate.or(*rate),
2148                            channels: *channels,
2149                        });
2150                    }
2151                }
2152            }
2153            StepDelta::Video {
2154                data,
2155                uri,
2156                mime_type,
2157                resolution,
2158            } => {
2159                if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2160                    &mut entry.step
2161                {
2162                    // Video may stream in multiple chunks; append base64 data
2163                    // to the previous video block when present.
2164                    if let (
2165                        Some(Content::Video {
2166                            data: Some(existing),
2167                            ..
2168                        }),
2169                        Some(new_data),
2170                    ) = (content.last_mut(), data.as_ref())
2171                    {
2172                        existing.push_str(new_data);
2173                    } else {
2174                        content.push(Content::Video {
2175                            data: data.clone(),
2176                            uri: uri.clone(),
2177                            mime_type: mime_type.clone(),
2178                            resolution: resolution.clone(),
2179                        });
2180                    }
2181                }
2182            }
2183            StepDelta::Document {
2184                data,
2185                uri,
2186                mime_type,
2187            } => {
2188                if let Step::UserInput { content } | Step::ModelOutput { content, .. } =
2189                    &mut entry.step
2190                {
2191                    // Documents may stream in multiple chunks; append base64
2192                    // data to the previous document block when present.
2193                    if let (
2194                        Some(Content::Document {
2195                            data: Some(existing),
2196                            ..
2197                        }),
2198                        Some(new_data),
2199                    ) = (content.last_mut(), data.as_ref())
2200                    {
2201                        existing.push_str(new_data);
2202                    } else {
2203                        content.push(Content::Document {
2204                            data: data.clone(),
2205                            uri: uri.clone(),
2206                            mime_type: mime_type.clone(),
2207                        });
2208                    }
2209                }
2210            }
2211            StepDelta::ThoughtSummary { content } => {
2212                if let Step::Thought { summary, .. } = &mut entry.step
2213                    && let Some(c) = content
2214                {
2215                    // Consecutive text summaries merge; other content appends.
2216                    if let (
2217                        Some(Content::Text { text: Some(t), .. }),
2218                        Content::Text {
2219                            text: Some(new), ..
2220                        },
2221                    ) = (summary.last_mut(), c)
2222                    {
2223                        t.push_str(new);
2224                    } else {
2225                        summary.push(c.clone());
2226                    }
2227                }
2228            }
2229            StepDelta::ThoughtSignature { signature } => {
2230                if let Step::Thought { signature: sig, .. } = &mut entry.step
2231                    && let Some(fragment) = signature
2232                {
2233                    sig.get_or_insert_with(String::new).push_str(fragment);
2234                }
2235            }
2236            StepDelta::ArgumentsDelta { arguments } => {
2237                entry.args_buffer.push_str(arguments);
2238            }
2239            StepDelta::FunctionResult {
2240                call_id,
2241                name,
2242                result,
2243                is_error,
2244            } => {
2245                // Preserve a signature delivered on step.start; the delta
2246                // payload does not carry one.
2247                let signature = match &entry.step {
2248                    Step::FunctionResult { signature, .. } => signature.clone(),
2249                    _ => None,
2250                };
2251                entry.step = Step::FunctionResult {
2252                    call_id: call_id.clone(),
2253                    name: name.clone(),
2254                    result: result.clone(),
2255                    is_error: *is_error,
2256                    signature,
2257                };
2258            }
2259            StepDelta::CodeExecutionCall {
2260                language,
2261                code,
2262                signature,
2263            } => {
2264                if let Step::CodeExecutionCall {
2265                    language: lang,
2266                    code: existing_code,
2267                    signature: sig,
2268                    ..
2269                } = &mut entry.step
2270                {
2271                    if let Some(l) = language {
2272                        *lang = l.clone();
2273                    }
2274                    if let Some(c) = code {
2275                        existing_code.push_str(c);
2276                    }
2277                    if signature.is_some() {
2278                        *sig = signature.clone();
2279                    }
2280                }
2281            }
2282            StepDelta::CodeExecutionResult {
2283                result,
2284                is_error,
2285                signature,
2286            } => {
2287                if let Step::CodeExecutionResult {
2288                    result: existing,
2289                    is_error: err,
2290                    signature: sig,
2291                    ..
2292                } = &mut entry.step
2293                {
2294                    existing.push_str(result);
2295                    if let Some(e) = is_error {
2296                        *err = *e;
2297                    }
2298                    if signature.is_some() {
2299                        *sig = signature.clone();
2300                    }
2301                }
2302            }
2303            StepDelta::UrlContextCall { urls, signature } => {
2304                if let Step::UrlContextCall {
2305                    urls: existing,
2306                    signature: sig,
2307                    ..
2308                } = &mut entry.step
2309                {
2310                    existing.extend(urls.iter().cloned());
2311                    if signature.is_some() {
2312                        *sig = signature.clone();
2313                    }
2314                }
2315            }
2316            StepDelta::UrlContextResult {
2317                result,
2318                is_error,
2319                signature,
2320            } => {
2321                if let Step::UrlContextResult {
2322                    result: existing,
2323                    is_error: err,
2324                    signature: sig,
2325                    ..
2326                } = &mut entry.step
2327                {
2328                    existing.extend(result.iter().cloned());
2329                    if is_error.is_some() {
2330                        *err = *is_error;
2331                    }
2332                    if signature.is_some() {
2333                        *sig = signature.clone();
2334                    }
2335                }
2336            }
2337            StepDelta::GoogleSearchCall { queries, signature } => {
2338                if let Step::GoogleSearchCall {
2339                    queries: existing,
2340                    signature: sig,
2341                    ..
2342                } = &mut entry.step
2343                {
2344                    existing.extend(queries.iter().cloned());
2345                    if signature.is_some() {
2346                        *sig = signature.clone();
2347                    }
2348                }
2349            }
2350            StepDelta::GoogleSearchResult {
2351                result,
2352                is_error,
2353                signature,
2354            } => {
2355                if let Step::GoogleSearchResult {
2356                    result: existing,
2357                    is_error: err,
2358                    signature: sig,
2359                    ..
2360                } = &mut entry.step
2361                {
2362                    existing.extend(result.iter().cloned());
2363                    if is_error.is_some() {
2364                        *err = *is_error;
2365                    }
2366                    if signature.is_some() {
2367                        *sig = signature.clone();
2368                    }
2369                }
2370            }
2371            StepDelta::McpServerToolCall {
2372                name,
2373                server_name,
2374                arguments,
2375            } => {
2376                if let Step::McpServerToolCall {
2377                    name: n,
2378                    server_name: sn,
2379                    arguments: args,
2380                    ..
2381                } = &mut entry.step
2382                {
2383                    *n = name.clone();
2384                    *sn = server_name.clone();
2385                    *args = arguments.clone();
2386                }
2387            }
2388            StepDelta::McpServerToolResult {
2389                name,
2390                server_name,
2391                result,
2392            } => {
2393                if let Step::McpServerToolResult {
2394                    name: n,
2395                    server_name: sn,
2396                    result: r,
2397                    ..
2398                } = &mut entry.step
2399                {
2400                    if name.is_some() {
2401                        *n = name.clone();
2402                    }
2403                    if server_name.is_some() {
2404                        *sn = server_name.clone();
2405                    }
2406                    *r = result.clone();
2407                }
2408            }
2409            StepDelta::FileSearchCall { signature } => {
2410                if let Step::FileSearchCall { signature: sig, .. } = &mut entry.step
2411                    && signature.is_some()
2412                {
2413                    *sig = signature.clone();
2414                }
2415            }
2416            StepDelta::FileSearchResult { result, signature } => {
2417                if let Step::FileSearchResult {
2418                    result: existing,
2419                    signature: sig,
2420                    ..
2421                } = &mut entry.step
2422                {
2423                    existing.extend(result.iter().cloned());
2424                    if signature.is_some() {
2425                        *sig = signature.clone();
2426                    }
2427                }
2428            }
2429            StepDelta::GoogleMapsCall { queries, signature } => {
2430                if let Step::GoogleMapsCall {
2431                    queries: existing,
2432                    signature: sig,
2433                    ..
2434                } = &mut entry.step
2435                {
2436                    existing.extend(queries.iter().cloned());
2437                    if signature.is_some() {
2438                        *sig = signature.clone();
2439                    }
2440                }
2441            }
2442            StepDelta::GoogleMapsResult { result, signature } => {
2443                if let Step::GoogleMapsResult {
2444                    result: existing,
2445                    signature: sig,
2446                    ..
2447                } = &mut entry.step
2448                {
2449                    existing.extend(result.iter().cloned());
2450                    if signature.is_some() {
2451                        *sig = signature.clone();
2452                    }
2453                }
2454            }
2455            StepDelta::Unknown { delta_type, .. } => {
2456                tracing::debug!(
2457                    "Skipping unknown StepDelta type '{}' during accumulation",
2458                    delta_type
2459                );
2460            }
2461        }
2462    }
2463
2464    /// Finalizes the step at `index` (called on `step.stop`).
2465    ///
2466    /// Parses any buffered `arguments_delta` fragments into the function
2467    /// call's `arguments`.
2468    pub(crate) fn stop(&mut self, index: usize) {
2469        if let Some(entry) = self.steps.get_mut(&index) {
2470            Self::finalize_entry(entry);
2471        }
2472    }
2473
2474    /// Records the cumulative interaction usage reported on a `step.stop`
2475    /// event, so it can serve as a fallback if the terminal event omits usage.
2476    pub(crate) fn record_cumulative_usage(&mut self, usage: crate::response::UsageMetadata) {
2477        self.last_cumulative_usage = Some(usage);
2478    }
2479
2480    /// Takes the last recorded cumulative usage, if any.
2481    pub(crate) fn take_cumulative_usage(&mut self) -> Option<crate::response::UsageMetadata> {
2482        self.last_cumulative_usage.take()
2483    }
2484
2485    fn finalize_entry(entry: &mut AccumulatedStep) {
2486        if entry.args_buffer.is_empty() {
2487            return;
2488        }
2489        if let Step::FunctionCall { arguments, .. } = &mut entry.step {
2490            match serde_json::from_str::<serde_json::Value>(&entry.args_buffer) {
2491                Ok(parsed) => *arguments = parsed,
2492                Err(e) => {
2493                    tracing::warn!(
2494                        "Failed to parse accumulated arguments_delta buffer as JSON: {}. \
2495                         Preserving raw string.",
2496                        e
2497                    );
2498                    *arguments = serde_json::Value::String(std::mem::take(&mut entry.args_buffer));
2499                }
2500            }
2501        }
2502        entry.args_buffer.clear();
2503    }
2504
2505    /// Consumes the accumulator and returns the ordered steps.
2506    pub(crate) fn finish(mut self) -> Vec<Step> {
2507        for entry in self.steps.values_mut() {
2508            Self::finalize_entry(entry);
2509        }
2510        self.steps.into_values().map(|e| e.step).collect()
2511    }
2512}
2513
2514#[cfg(test)]
2515mod tests {
2516    use super::*;
2517    use serde_json::json;
2518
2519    // =========================================================================
2520    // Step wire fixtures (shapes derived from google-genai 2.10 generated
2521    // bindings for API revision 2026-05-20)
2522    // =========================================================================
2523
2524    #[test]
2525    fn test_step_user_input_roundtrip() {
2526        let json_str = r#"{"type":"user_input","content":[{"type":"text","text":"Hello"}]}"#;
2527        let step: Step = serde_json::from_str(json_str).unwrap();
2528        assert!(matches!(&step, Step::UserInput { content } if content.len() == 1));
2529        assert_eq!(step.as_text(), Some("Hello"));
2530
2531        let out = serde_json::to_value(&step).unwrap();
2532        assert_eq!(out["type"], "user_input");
2533        assert_eq!(out["content"][0]["text"], "Hello");
2534    }
2535
2536    #[test]
2537    fn test_step_model_output_with_error() {
2538        let json_str = r#"{
2539            "type": "model_output",
2540            "content": [{"type": "text", "text": "Partial"}],
2541            "error": {"code": 8, "message": "quota exhausted"}
2542        }"#;
2543        let step: Step = serde_json::from_str(json_str).unwrap();
2544        match &step {
2545            Step::ModelOutput { content, error } => {
2546                assert_eq!(content.len(), 1);
2547                let error = error.as_ref().unwrap();
2548                assert_eq!(error.code, Some(8));
2549                assert_eq!(error.message.as_deref(), Some("quota exhausted"));
2550            }
2551            other => panic!("Expected ModelOutput, got {other:?}"),
2552        }
2553    }
2554
2555    #[test]
2556    fn test_step_thought_roundtrip() {
2557        let json_str = r#"{
2558            "type": "thought",
2559            "signature": "sig-abc",
2560            "summary": [{"type": "text", "text": "Thinking about it"}]
2561        }"#;
2562        let step: Step = serde_json::from_str(json_str).unwrap();
2563        match &step {
2564            Step::Thought { signature, summary } => {
2565                assert_eq!(signature.as_deref(), Some("sig-abc"));
2566                assert_eq!(summary.len(), 1);
2567            }
2568            other => panic!("Expected Thought, got {other:?}"),
2569        }
2570        assert_eq!(step.signature(), Some("sig-abc"));
2571
2572        let out = serde_json::to_value(&step).unwrap();
2573        assert_eq!(out["type"], "thought");
2574        assert_eq!(out["signature"], "sig-abc");
2575    }
2576
2577    #[test]
2578    fn test_step_function_call_roundtrip() {
2579        let json_str = r#"{
2580            "type": "function_call",
2581            "id": "call_1",
2582            "name": "get_weather",
2583            "arguments": {"city": "Tokyo"}
2584        }"#;
2585        let step: Step = serde_json::from_str(json_str).unwrap();
2586        match &step {
2587            Step::FunctionCall {
2588                id,
2589                name,
2590                arguments,
2591                signature,
2592            } => {
2593                assert_eq!(id, "call_1");
2594                assert_eq!(name, "get_weather");
2595                assert_eq!(arguments["city"], "Tokyo");
2596                assert_eq!(*signature, None);
2597            }
2598            other => panic!("Expected FunctionCall, got {other:?}"),
2599        }
2600
2601        let out = serde_json::to_value(&step).unwrap();
2602        assert_eq!(out["type"], "function_call");
2603        assert_eq!(out["id"], "call_1");
2604        assert_eq!(out["arguments"]["city"], "Tokyo");
2605        // No signature was present, so none is emitted.
2606        assert!(out.get("signature").is_none());
2607    }
2608
2609    #[test]
2610    fn test_step_function_call_roundtrip_preserves_signature() {
2611        // Verified live 2026-07: the API returns `signature` on function_call
2612        // steps and rejects stateless replay without it.
2613        let json_str = r#"{
2614            "type": "function_call",
2615            "id": "call_2",
2616            "name": "get_weather",
2617            "arguments": {"city": "Paris"},
2618            "signature": "sig-fc-123"
2619        }"#;
2620        let step: Step = serde_json::from_str(json_str).unwrap();
2621        match &step {
2622            Step::FunctionCall { signature, .. } => {
2623                assert_eq!(signature.as_deref(), Some("sig-fc-123"));
2624            }
2625            other => panic!("Expected FunctionCall, got {other:?}"),
2626        }
2627        assert_eq!(step.signature(), Some("sig-fc-123"));
2628
2629        let out = serde_json::to_value(&step).unwrap();
2630        assert_eq!(out["type"], "function_call");
2631        assert_eq!(out["signature"], "sig-fc-123");
2632    }
2633
2634    #[test]
2635    fn test_step_function_result_roundtrip_preserves_signature() {
2636        let json_str = r#"{
2637            "type": "function_result",
2638            "call_id": "call_2",
2639            "name": "get_weather",
2640            "result": "sunny",
2641            "signature": "sig-fr-456"
2642        }"#;
2643        let step: Step = serde_json::from_str(json_str).unwrap();
2644        match &step {
2645            Step::FunctionResult { signature, .. } => {
2646                assert_eq!(signature.as_deref(), Some("sig-fr-456"));
2647            }
2648            other => panic!("Expected FunctionResult, got {other:?}"),
2649        }
2650        assert_eq!(step.signature(), Some("sig-fr-456"));
2651
2652        let out = serde_json::to_value(&step).unwrap();
2653        assert_eq!(out["type"], "function_result");
2654        assert_eq!(out["signature"], "sig-fr-456");
2655
2656        // Constructors leave the signature unset.
2657        let constructed = Step::function_result("get_weather", "call_2", "sunny");
2658        let out = serde_json::to_value(&constructed).unwrap();
2659        assert!(out.get("signature").is_none());
2660    }
2661
2662    #[test]
2663    fn test_step_function_result_payload_union() {
2664        // String result
2665        let s: Step = serde_json::from_str(
2666            r#"{"type":"function_result","call_id":"c1","result":"22 degrees"}"#,
2667        )
2668        .unwrap();
2669        match &s {
2670            Step::FunctionResult { result, .. } => {
2671                assert_eq!(result.as_text(), Some("22 degrees"));
2672            }
2673            other => panic!("Expected FunctionResult, got {other:?}"),
2674        }
2675
2676        // Object result
2677        let s: Step = serde_json::from_str(
2678            r#"{"type":"function_result","call_id":"c2","result":{"temp":22},"is_error":false}"#,
2679        )
2680        .unwrap();
2681        match &s {
2682            Step::FunctionResult {
2683                result, is_error, ..
2684            } => {
2685                assert_eq!(result.as_json().unwrap()["temp"], 22);
2686                assert_eq!(*is_error, Some(false));
2687            }
2688            other => panic!("Expected FunctionResult, got {other:?}"),
2689        }
2690
2691        // Content-block list result
2692        let s: Step = serde_json::from_str(
2693            r#"{"type":"function_result","call_id":"c3","result":[{"type":"text","text":"hi"}]}"#,
2694        )
2695        .unwrap();
2696        match &s {
2697            Step::FunctionResult { result, .. } => {
2698                let contents = result.as_contents().unwrap();
2699                assert_eq!(contents.len(), 1);
2700                assert_eq!(contents[0].as_text(), Some("hi"));
2701            }
2702            other => panic!("Expected FunctionResult, got {other:?}"),
2703        }
2704    }
2705
2706    #[test]
2707    fn test_step_code_execution_nested_arguments_roundtrip() {
2708        let json_str = r#"{
2709            "type": "code_execution_call",
2710            "id": "exec_1",
2711            "arguments": {"language": "python", "code": "print(42)"},
2712            "signature": "sig-1"
2713        }"#;
2714        let step: Step = serde_json::from_str(json_str).unwrap();
2715        match &step {
2716            Step::CodeExecutionCall {
2717                id,
2718                language,
2719                code,
2720                signature,
2721            } => {
2722                assert_eq!(id, "exec_1");
2723                assert_eq!(*language, CodeExecutionLanguage::Python);
2724                assert_eq!(code, "print(42)");
2725                assert_eq!(signature.as_deref(), Some("sig-1"));
2726            }
2727            other => panic!("Expected CodeExecutionCall, got {other:?}"),
2728        }
2729
2730        // Serialization nests language/code back under arguments.
2731        let out = serde_json::to_value(&step).unwrap();
2732        assert_eq!(out["arguments"]["language"], "python");
2733        assert_eq!(out["arguments"]["code"], "print(42)");
2734        assert_eq!(out["signature"], "sig-1");
2735    }
2736
2737    #[test]
2738    fn test_step_google_search_call_with_search_type() {
2739        let json_str = r#"{
2740            "type": "google_search_call",
2741            "id": "s1",
2742            "arguments": {"queries": ["rust serde"]},
2743            "search_type": "web_search",
2744            "signature": "sig"
2745        }"#;
2746        let step: Step = serde_json::from_str(json_str).unwrap();
2747        match &step {
2748            Step::GoogleSearchCall {
2749                queries,
2750                search_type,
2751                ..
2752            } => {
2753                assert_eq!(queries, &["rust serde".to_string()]);
2754                assert!(matches!(
2755                    search_type,
2756                    Some(crate::tools::SearchType::WebSearch)
2757                ));
2758            }
2759            other => panic!("Expected GoogleSearchCall, got {other:?}"),
2760        }
2761    }
2762
2763    #[test]
2764    fn test_step_mcp_server_tool_call_roundtrip() {
2765        let json_str = r#"{
2766            "type": "mcp_server_tool_call",
2767            "id": "m1",
2768            "name": "read_file",
2769            "server_name": "fs",
2770            "arguments": {"path": "/tmp/x"}
2771        }"#;
2772        let step: Step = serde_json::from_str(json_str).unwrap();
2773        match &step {
2774            Step::McpServerToolCall {
2775                name, server_name, ..
2776            } => {
2777                assert_eq!(name, "read_file");
2778                assert_eq!(server_name, "fs");
2779            }
2780            other => panic!("Expected McpServerToolCall, got {other:?}"),
2781        }
2782        let out = serde_json::to_value(&step).unwrap();
2783        assert_eq!(out["server_name"], "fs");
2784    }
2785
2786    #[test]
2787    #[cfg(not(feature = "strict-unknown"))]
2788    fn test_step_unknown_preserves_data_and_roundtrips() {
2789        let json_str = r#"{"type":"quantum_step","novel_field":42}"#;
2790        let step: Step = serde_json::from_str(json_str).unwrap();
2791        assert!(step.is_unknown());
2792        assert_eq!(step.unknown_step_type(), Some("quantum_step"));
2793        assert_eq!(step.unknown_data().unwrap()["novel_field"], 42);
2794
2795        let out = serde_json::to_value(&step).unwrap();
2796        assert_eq!(out["type"], "quantum_step");
2797        assert_eq!(out["novel_field"], 42);
2798    }
2799
2800    #[test]
2801    fn test_step_type_accessor() {
2802        assert_eq!(Step::user_text("x").step_type(), "user_input");
2803        assert_eq!(
2804            Step::function_call("id", "f", json!({})).step_type(),
2805            "function_call"
2806        );
2807        assert_eq!(
2808            Step::Unknown {
2809                step_type: "future".into(),
2810                data: serde_json::Value::Null
2811            }
2812            .step_type(),
2813            "future"
2814        );
2815    }
2816
2817    // =========================================================================
2818    // StepDelta wire fixtures
2819    // =========================================================================
2820
2821    #[test]
2822    fn test_step_delta_text() {
2823        let delta: StepDelta = serde_json::from_str(r#"{"type":"text","text":"Hel"}"#).unwrap();
2824        assert_eq!(delta.as_text(), Some("Hel"));
2825        let out = serde_json::to_value(&delta).unwrap();
2826        assert_eq!(out, json!({"type": "text", "text": "Hel"}));
2827    }
2828
2829    #[test]
2830    fn test_step_delta_arguments_delta() {
2831        let delta: StepDelta =
2832            serde_json::from_str(r#"{"type":"arguments_delta","arguments":"{\"city\": \"To"}"#)
2833                .unwrap();
2834        assert_eq!(delta.as_arguments_delta(), Some("{\"city\": \"To"));
2835    }
2836
2837    #[test]
2838    fn test_step_delta_audio_with_rate_and_channels() {
2839        let delta: StepDelta = serde_json::from_str(
2840            r#"{"type":"audio","data":"QUJD","mime_type":"audio/l16","rate":24000,"sample_rate":24000,"channels":1}"#,
2841        )
2842        .unwrap();
2843        match &delta {
2844            StepDelta::Audio {
2845                sample_rate,
2846                channels,
2847                rate,
2848                ..
2849            } => {
2850                assert_eq!(*sample_rate, Some(24000));
2851                assert_eq!(*rate, Some(24000));
2852                assert_eq!(*channels, Some(1));
2853            }
2854            other => panic!("Expected Audio, got {other:?}"),
2855        }
2856    }
2857
2858    #[test]
2859    fn test_step_delta_thought_summary_and_signature() {
2860        let summary: StepDelta = serde_json::from_str(
2861            r#"{"type":"thought_summary","content":{"type":"text","text":"Analyzing"}}"#,
2862        )
2863        .unwrap();
2864        assert!(matches!(summary, StepDelta::ThoughtSummary { .. }));
2865
2866        let sig: StepDelta =
2867            serde_json::from_str(r#"{"type":"thought_signature","signature":"abc123"}"#).unwrap();
2868        assert!(matches!(
2869            sig,
2870            StepDelta::ThoughtSignature { signature: Some(s) } if s == "abc123"
2871        ));
2872    }
2873
2874    #[test]
2875    fn test_step_delta_text_annotation() {
2876        let delta: StepDelta = serde_json::from_str(
2877            r#"{"type":"text_annotation_delta","annotations":[
2878                {"type":"url_citation","url":"https://example.com","title":"Example","start_index":0,"end_index":5}
2879            ]}"#,
2880        )
2881        .unwrap();
2882        match &delta {
2883            StepDelta::TextAnnotation { annotations } => {
2884                assert_eq!(annotations.len(), 1);
2885                assert!(matches!(annotations[0], Annotation::UrlCitation { .. }));
2886            }
2887            other => panic!("Expected TextAnnotation, got {other:?}"),
2888        }
2889    }
2890
2891    #[test]
2892    fn test_step_delta_unknown_roundtrip() {
2893        let delta: StepDelta = serde_json::from_str(r#"{"type":"hologram","frames":3}"#).unwrap();
2894        assert!(delta.is_unknown());
2895        assert_eq!(delta.unknown_delta_type(), Some("hologram"));
2896        assert_eq!(delta.unknown_data().unwrap()["frames"], 3);
2897        let out = serde_json::to_value(&delta).unwrap();
2898        assert_eq!(out["type"], "hologram");
2899        assert_eq!(out["frames"], 3);
2900    }
2901
2902    // =========================================================================
2903    // FunctionResultPayload
2904    // =========================================================================
2905
2906    #[test]
2907    fn test_function_result_payload_from_value() {
2908        assert!(matches!(
2909            FunctionResultPayload::from_value(json!("text")),
2910            FunctionResultPayload::Text(_)
2911        ));
2912        assert!(matches!(
2913            FunctionResultPayload::from_value(json!({"a": 1})),
2914            FunctionResultPayload::Json(_)
2915        ));
2916        assert!(matches!(
2917            FunctionResultPayload::from_value(json!([{"type": "text", "text": "hi"}])),
2918            FunctionResultPayload::Contents(_)
2919        ));
2920        // Non-content array stays raw JSON
2921        assert!(matches!(
2922            FunctionResultPayload::from_value(json!([1, 2, 3])),
2923            FunctionResultPayload::Json(_)
2924        ));
2925    }
2926
2927    #[test]
2928    fn test_function_result_payload_roundtrip() {
2929        for payload in [
2930            FunctionResultPayload::Text("hello".into()),
2931            FunctionResultPayload::Json(json!({"k": [1, 2]})),
2932            FunctionResultPayload::Contents(vec![Content::text("block")]),
2933        ] {
2934            let serialized = serde_json::to_string(&payload).unwrap();
2935            let back: FunctionResultPayload = serde_json::from_str(&serialized).unwrap();
2936            assert_eq!(payload, back);
2937        }
2938    }
2939
2940    // =========================================================================
2941    // StepAccumulator
2942    // =========================================================================
2943
2944    #[test]
2945    fn test_accumulator_text_stream() {
2946        let mut acc = StepAccumulator::new();
2947        acc.start(0, Step::model_output(vec![]));
2948        acc.apply_delta(
2949            0,
2950            &StepDelta::Text {
2951                text: "Hello ".into(),
2952            },
2953        );
2954        acc.apply_delta(
2955            0,
2956            &StepDelta::Text {
2957                text: "world".into(),
2958            },
2959        );
2960        acc.stop(0);
2961        let steps = acc.finish();
2962        assert_eq!(steps.len(), 1);
2963        assert_eq!(steps[0].as_text(), Some("Hello world"));
2964    }
2965
2966    #[test]
2967    fn test_accumulator_image_deltas_accumulate_into_one_block() {
2968        let mut acc = StepAccumulator::new();
2969        acc.start(0, Step::model_output(vec![]));
2970        acc.apply_delta(
2971            0,
2972            &StepDelta::Image {
2973                data: Some("AAAA".into()),
2974                uri: None,
2975                mime_type: Some("image/png".into()),
2976                resolution: None,
2977            },
2978        );
2979        acc.apply_delta(
2980            0,
2981            &StepDelta::Image {
2982                data: Some("BBBB".into()),
2983                uri: None,
2984                mime_type: Some("image/png".into()),
2985                resolution: None,
2986            },
2987        );
2988        acc.stop(0);
2989        let steps = acc.finish();
2990        match &steps[0] {
2991            Step::ModelOutput { content, .. } => {
2992                assert_eq!(content.len(), 1);
2993                match &content[0] {
2994                    Content::Image { data, .. } => assert_eq!(data.as_deref(), Some("AAAABBBB")),
2995                    other => panic!("Expected Image, got {other:?}"),
2996                }
2997            }
2998            other => panic!("Expected ModelOutput, got {other:?}"),
2999        }
3000    }
3001
3002    #[test]
3003    fn test_accumulator_video_and_document_deltas_accumulate() {
3004        let mut acc = StepAccumulator::new();
3005        acc.start(0, Step::model_output(vec![]));
3006        acc.apply_delta(
3007            0,
3008            &StepDelta::Video {
3009                data: Some("VV".into()),
3010                uri: None,
3011                mime_type: Some("video/mp4".into()),
3012                resolution: None,
3013            },
3014        );
3015        acc.apply_delta(
3016            0,
3017            &StepDelta::Video {
3018                data: Some("WW".into()),
3019                uri: None,
3020                mime_type: Some("video/mp4".into()),
3021                resolution: None,
3022            },
3023        );
3024        acc.apply_delta(
3025            0,
3026            &StepDelta::Document {
3027                data: Some("DD".into()),
3028                uri: None,
3029                mime_type: Some("application/pdf".into()),
3030            },
3031        );
3032        acc.apply_delta(
3033            0,
3034            &StepDelta::Document {
3035                data: Some("EE".into()),
3036                uri: None,
3037                mime_type: Some("application/pdf".into()),
3038            },
3039        );
3040        acc.stop(0);
3041        let steps = acc.finish();
3042        match &steps[0] {
3043            Step::ModelOutput { content, .. } => {
3044                assert_eq!(content.len(), 2);
3045                match &content[0] {
3046                    Content::Video { data, .. } => assert_eq!(data.as_deref(), Some("VVWW")),
3047                    other => panic!("Expected Video, got {other:?}"),
3048                }
3049                match &content[1] {
3050                    Content::Document { data, .. } => assert_eq!(data.as_deref(), Some("DDEE")),
3051                    other => panic!("Expected Document, got {other:?}"),
3052                }
3053            }
3054            other => panic!("Expected ModelOutput, got {other:?}"),
3055        }
3056    }
3057
3058    #[test]
3059    fn test_accumulator_image_uri_delta_pushes_new_block() {
3060        // Deltas without inline data (e.g. URI references) must not merge
3061        // into the previous block.
3062        let mut acc = StepAccumulator::new();
3063        acc.start(0, Step::model_output(vec![]));
3064        acc.apply_delta(
3065            0,
3066            &StepDelta::Image {
3067                data: Some("AAAA".into()),
3068                uri: None,
3069                mime_type: Some("image/png".into()),
3070                resolution: None,
3071            },
3072        );
3073        acc.apply_delta(
3074            0,
3075            &StepDelta::Image {
3076                data: None,
3077                uri: Some("https://example.com/img.png".into()),
3078                mime_type: Some("image/png".into()),
3079                resolution: None,
3080            },
3081        );
3082        acc.stop(0);
3083        let steps = acc.finish();
3084        match &steps[0] {
3085            Step::ModelOutput { content, .. } => assert_eq!(content.len(), 2),
3086            other => panic!("Expected ModelOutput, got {other:?}"),
3087        }
3088    }
3089
3090    #[test]
3091    fn test_accumulator_function_call_arguments_delta() {
3092        let mut acc = StepAccumulator::new();
3093        acc.start(
3094            0,
3095            Step::FunctionCall {
3096                id: "c1".into(),
3097                name: "get_weather".into(),
3098                arguments: serde_json::Value::Null,
3099                signature: None,
3100            },
3101        );
3102        acc.apply_delta(
3103            0,
3104            &StepDelta::ArgumentsDelta {
3105                arguments: "{\"city\": ".into(),
3106            },
3107        );
3108        acc.apply_delta(
3109            0,
3110            &StepDelta::ArgumentsDelta {
3111                arguments: "\"Tokyo\"}".into(),
3112            },
3113        );
3114        acc.stop(0);
3115        let steps = acc.finish();
3116        match &steps[0] {
3117            Step::FunctionCall { arguments, .. } => assert_eq!(arguments["city"], "Tokyo"),
3118            other => panic!("Expected FunctionCall, got {other:?}"),
3119        }
3120    }
3121
3122    #[test]
3123    fn test_accumulator_thought_summary_and_signature() {
3124        let mut acc = StepAccumulator::new();
3125        acc.start(
3126            0,
3127            Step::Thought {
3128                signature: None,
3129                summary: vec![],
3130            },
3131        );
3132        acc.apply_delta(
3133            0,
3134            &StepDelta::ThoughtSummary {
3135                content: Some(Content::text("Consider ")),
3136            },
3137        );
3138        acc.apply_delta(
3139            0,
3140            &StepDelta::ThoughtSummary {
3141                content: Some(Content::text("the problem")),
3142            },
3143        );
3144        acc.apply_delta(
3145            0,
3146            &StepDelta::ThoughtSignature {
3147                signature: Some("sig-xyz".into()),
3148            },
3149        );
3150        let steps = acc.finish();
3151        match &steps[0] {
3152            Step::Thought { signature, summary } => {
3153                assert_eq!(signature.as_deref(), Some("sig-xyz"));
3154                assert_eq!(summary[0].as_text(), Some("Consider the problem"));
3155            }
3156            other => panic!("Expected Thought, got {other:?}"),
3157        }
3158    }
3159
3160    #[test]
3161    fn test_accumulator_delta_without_start_creates_model_output() {
3162        let mut acc = StepAccumulator::new();
3163        acc.apply_delta(
3164            0,
3165            &StepDelta::Text {
3166                text: "orphan".into(),
3167            },
3168        );
3169        let steps = acc.finish();
3170        assert_eq!(steps[0].as_text(), Some("orphan"));
3171    }
3172
3173    #[test]
3174    fn test_accumulator_orders_steps_by_index() {
3175        let mut acc = StepAccumulator::new();
3176        acc.start(2, Step::model_text("second"));
3177        acc.start(1, Step::thought("sig"));
3178        let steps = acc.finish();
3179        assert!(matches!(steps[0], Step::Thought { .. }));
3180        assert!(matches!(steps[1], Step::ModelOutput { .. }));
3181    }
3182}