Skip to main content

rig_core/test_utils/
streaming.rs

1//! Streaming helpers for [`MockCompletionModel`](super::MockCompletionModel).
2
3use crate::{
4    completion::{CompletionError, GetTokenUsage, Usage},
5    message::ReasoningContent,
6    streaming::{RawStreamingChoice, RawStreamingToolCall, ToolCallDeltaContent},
7};
8use serde::{Deserialize, Serialize};
9
10/// Raw mock response used by completion and streaming test utilities.
11#[derive(Clone, Debug, Default, Deserialize, Serialize)]
12pub struct MockResponse {
13    usage: Usage,
14}
15
16impl MockResponse {
17    /// Create a mock raw response without token usage (zero-valued usage,
18    /// [`Usage`]'s documented sentinel for missing provider metrics).
19    pub fn new() -> Self {
20        Self {
21            usage: Usage::new(),
22        }
23    }
24
25    /// Create a mock raw response carrying token usage.
26    pub fn with_usage(usage: Usage) -> Self {
27        Self { usage }
28    }
29
30    /// Create a mock raw response whose usage has only `total_tokens` set.
31    pub fn with_total_tokens(total_tokens: u64) -> Self {
32        let mut usage = Usage::new();
33        usage.total_tokens = total_tokens;
34        Self::with_usage(usage)
35    }
36}
37
38impl GetTokenUsage for MockResponse {
39    fn token_usage(&self) -> Usage {
40        self.usage
41    }
42}
43
44/// Scripted streaming event yielded by [`MockCompletionModel`](super::MockCompletionModel).
45#[derive(Clone, Debug)]
46pub enum MockStreamEvent {
47    /// Text chunk.
48    Text(String),
49    /// Start a new text content block with optional provider metadata.
50    TextStart {
51        additional_params: Option<serde_json::Value>,
52    },
53    /// Provider-specific metadata for the current text content block.
54    TextAdditionalParams(serde_json::Value),
55    /// Complete tool call event.
56    ToolCall {
57        id: String,
58        name: String,
59        arguments: serde_json::Value,
60        call_id: Option<String>,
61    },
62    /// Tool call delta event.
63    ToolCallDelta {
64        id: String,
65        internal_call_id: String,
66        content: ToolCallDeltaContent,
67    },
68    /// Complete reasoning event.
69    Reasoning {
70        id: Option<String>,
71        content: ReasoningContent,
72    },
73    /// Reasoning delta event.
74    ReasoningDelta {
75        id: Option<String>,
76        reasoning: String,
77    },
78    /// Provider-assigned message ID.
79    MessageId(String),
80    /// Provider-native output item that Rig does not model.
81    Unknown(serde_json::Value),
82    /// Final raw response carrying optional usage.
83    FinalResponse(MockResponse),
84    /// Stream error.
85    Error(MockError),
86}
87
88use super::completion::MockError;
89
90impl MockStreamEvent {
91    /// Create a text chunk.
92    pub fn text(text: impl Into<String>) -> Self {
93        Self::Text(text.into())
94    }
95
96    /// Start a new text content block.
97    pub fn text_start(additional_params: Option<serde_json::Value>) -> Self {
98        Self::TextStart { additional_params }
99    }
100
101    /// Add provider-specific metadata to the current text content block.
102    pub fn text_additional_params(additional_params: serde_json::Value) -> Self {
103        Self::TextAdditionalParams(additional_params)
104    }
105
106    /// Create a complete tool call event.
107    pub fn tool_call(
108        id: impl Into<String>,
109        name: impl Into<String>,
110        arguments: serde_json::Value,
111    ) -> Self {
112        Self::ToolCall {
113            id: id.into(),
114            name: name.into(),
115            arguments,
116            call_id: None,
117        }
118    }
119
120    /// Attach a provider-specific call ID to a complete tool call event.
121    pub fn with_call_id(mut self, call_id: impl Into<String>) -> Self {
122        if let Self::ToolCall { call_id: id, .. } = &mut self {
123            *id = Some(call_id.into());
124        }
125        self
126    }
127
128    /// Create a tool call name delta.
129    pub fn tool_call_name_delta(
130        id: impl Into<String>,
131        internal_call_id: impl Into<String>,
132        name: impl Into<String>,
133    ) -> Self {
134        Self::ToolCallDelta {
135            id: id.into(),
136            internal_call_id: internal_call_id.into(),
137            content: ToolCallDeltaContent::Name(name.into()),
138        }
139    }
140
141    /// Create a tool call arguments delta.
142    pub fn tool_call_arguments_delta(
143        id: impl Into<String>,
144        internal_call_id: impl Into<String>,
145        arguments: impl Into<String>,
146    ) -> Self {
147        Self::ToolCallDelta {
148            id: id.into(),
149            internal_call_id: internal_call_id.into(),
150            content: ToolCallDeltaContent::Delta(arguments.into()),
151        }
152    }
153
154    /// Create a complete reasoning event.
155    pub fn reasoning(reasoning: impl Into<String>) -> Self {
156        Self::Reasoning {
157            id: None,
158            content: ReasoningContent::Text {
159                text: reasoning.into(),
160                signature: None,
161            },
162        }
163    }
164
165    /// Attach a provider-specific reasoning ID to a complete reasoning event.
166    pub fn with_reasoning_id(mut self, reasoning_id: impl Into<String>) -> Self {
167        if let Self::Reasoning { id, .. } = &mut self {
168            *id = Some(reasoning_id.into());
169        }
170        self
171    }
172
173    /// Create a reasoning delta event.
174    pub fn reasoning_delta(id: Option<impl Into<String>>, reasoning: impl Into<String>) -> Self {
175        Self::ReasoningDelta {
176            id: id.map(Into::into),
177            reasoning: reasoning.into(),
178        }
179    }
180
181    /// Create a provider-assigned message ID event.
182    pub fn message_id(id: impl Into<String>) -> Self {
183        Self::MessageId(id.into())
184    }
185
186    /// Create an unmodeled provider output item.
187    pub fn unknown(value: serde_json::Value) -> Self {
188        Self::Unknown(value)
189    }
190
191    /// Create a final response event with usage.
192    pub fn final_response(usage: Usage) -> Self {
193        Self::FinalResponse(MockResponse::with_usage(usage))
194    }
195
196    /// Create a final response event with default zero usage.
197    pub fn final_response_with_default_usage() -> Self {
198        Self::FinalResponse(MockResponse::with_usage(Usage::new()))
199    }
200
201    /// Create a final response event whose usage has only `total_tokens` set.
202    pub fn final_response_with_total_tokens(total_tokens: u64) -> Self {
203        Self::FinalResponse(MockResponse::with_total_tokens(total_tokens))
204    }
205
206    /// Create a stream error event.
207    pub fn error(message: impl Into<String>) -> Self {
208        Self::Error(MockError::provider(message))
209    }
210
211    pub(crate) fn into_raw_choice(
212        self,
213    ) -> Result<RawStreamingChoice<MockResponse>, CompletionError> {
214        match self {
215            Self::Text(text) => Ok(RawStreamingChoice::Message(text)),
216            Self::TextStart { additional_params } => {
217                Ok(RawStreamingChoice::TextStart { additional_params })
218            }
219            Self::TextAdditionalParams(additional_params) => {
220                Ok(RawStreamingChoice::TextAdditionalParams(additional_params))
221            }
222            Self::ToolCall {
223                id,
224                name,
225                arguments,
226                call_id,
227            } => {
228                let mut tool_call = RawStreamingToolCall::new(id, name, arguments);
229                if let Some(call_id) = call_id {
230                    tool_call = tool_call.with_call_id(call_id);
231                }
232                Ok(RawStreamingChoice::ToolCall(tool_call))
233            }
234            Self::ToolCallDelta {
235                id,
236                internal_call_id,
237                content,
238            } => Ok(RawStreamingChoice::ToolCallDelta {
239                id,
240                internal_call_id,
241                content,
242            }),
243            Self::Reasoning { id, content } => Ok(RawStreamingChoice::Reasoning { id, content }),
244            Self::ReasoningDelta { id, reasoning } => {
245                Ok(RawStreamingChoice::ReasoningDelta { id, reasoning })
246            }
247            Self::MessageId(id) => Ok(RawStreamingChoice::MessageId(id)),
248            Self::Unknown(value) => Ok(RawStreamingChoice::Unknown(value)),
249            Self::FinalResponse(response) => Ok(RawStreamingChoice::FinalResponse(response)),
250            Self::Error(error) => Err(error.into_completion_error()),
251        }
252    }
253}