1use std::collections::HashMap;
6
7use crate::completion::{CompletionResponse, Usage};
8use crate::error::ProviderError;
9use crate::operation::{Block, CallFragment, Completion, Finish};
10use crate::wire::{Decoder, Flow, Out, WireEvent};
11
12pub const MOCK_PROVIDER: &str = "mock";
14
15pub fn mock_final(usage: Usage) -> Finish {
17 Finish {
18 usage,
19 ..Finish::default()
20 }
21}
22
23fn fixture_item(value: serde_json::Value) -> Result<Option<serde_json::Value>, ProviderError> {
27 match value {
28 serde_json::Value::Null => Ok(None),
29 serde_json::Value::Object(map) if map.is_empty() => Ok(None),
30 serde_json::Value::Object(map) => Ok(Some(serde_json::Value::Object(map))),
31 other => Err(ProviderError::Provider(format!(
32 "mock stream fixture provider item must be a JSON object, got: {other}"
33 ))),
34 }
35}
36
37pub fn mock_final_with_total_tokens(total_tokens: u64) -> Finish {
39 mock_final(Usage {
40 total_tokens: Some(total_tokens),
41 ..Default::default()
42 })
43}
44
45#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
47pub enum MockStreamEvent {
48 Text(String),
50 TextStart {
52 id: String,
53 additional_params: Option<serde_json::Value>,
54 },
55 TextAdditionalParams(serde_json::Value),
57 ToolCall {
59 id: String,
60 name: String,
61 arguments: serde_json::Value,
62 call_id: Option<String>,
63 },
64 ToolCallNameDelta { id: String, name: String },
66 ToolCallArgumentsDelta { id: String, arguments: String },
68 ToolCallEnd { id: String },
71 Reasoning { id: String, text: String },
73 ReasoningDelta { id: String, reasoning: String },
75 Unknown(serde_json::Value),
77 RequestId(String),
82 FinalResponse(Finish),
84 Error(MockError),
86}
87
88use super::completion::MockError;
89
90fn fixture_provider_id(id: &str) -> Option<&str> {
95 let unnamed = ["reasoning-", "block-", "output-", "tool-", "text-"]
96 .iter()
97 .any(|namespace| {
98 id.strip_prefix(namespace)
99 .is_some_and(|rest| rest.parse::<u64>().is_ok())
100 });
101 (!id.is_empty() && !unnamed).then_some(id)
102}
103
104impl MockStreamEvent {
105 pub fn text(text: impl Into<String>) -> Self {
107 Self::Text(text.into())
108 }
109
110 pub fn text_start(id: impl Into<String>, additional_params: Option<serde_json::Value>) -> Self {
112 Self::TextStart {
113 id: id.into(),
114 additional_params,
115 }
116 }
117
118 pub fn tool_call(
120 id: impl Into<String>,
121 name: impl Into<String>,
122 arguments: serde_json::Value,
123 ) -> Self {
124 Self::ToolCall {
125 id: id.into(),
126 name: name.into(),
127 arguments,
128 call_id: None,
129 }
130 }
131
132 pub fn with_call_id(mut self, call_id: impl Into<String>) -> Self {
134 if let Self::ToolCall { call_id: id, .. } = &mut self {
135 *id = Some(call_id.into());
136 }
137 self
138 }
139
140 pub fn tool_call_name_delta(id: impl Into<String>, name: impl Into<String>) -> Self {
142 Self::ToolCallNameDelta {
143 id: id.into(),
144 name: name.into(),
145 }
146 }
147
148 pub fn tool_call_arguments_delta(id: impl Into<String>, arguments: impl Into<String>) -> Self {
150 Self::ToolCallArgumentsDelta {
151 id: id.into(),
152 arguments: arguments.into(),
153 }
154 }
155
156 pub fn tool_call_end(id: impl Into<String>) -> Self {
158 Self::ToolCallEnd { id: id.into() }
159 }
160
161 pub fn reasoning(reasoning: impl Into<String>) -> Self {
165 Self::Reasoning {
166 id: "reasoning-0".to_string(),
167 text: reasoning.into(),
168 }
169 }
170
171 pub fn with_reasoning_id(mut self, reasoning_id: impl Into<String>) -> Self {
173 if let Self::Reasoning { id, .. } = &mut self {
174 *id = reasoning_id.into();
175 }
176 self
177 }
178
179 pub fn reasoning_delta(reasoning: impl Into<String>) -> Self {
183 Self::reasoning_delta_with_id("reasoning-0", reasoning)
184 }
185
186 pub fn reasoning_delta_with_id(id: impl Into<String>, reasoning: impl Into<String>) -> Self {
188 Self::ReasoningDelta {
189 id: id.into(),
190 reasoning: reasoning.into(),
191 }
192 }
193
194 pub fn unknown(value: serde_json::Value) -> Self {
196 Self::Unknown(value)
197 }
198
199 pub fn final_response(usage: Usage) -> Self {
201 Self::FinalResponse(mock_final(usage))
202 }
203
204 pub fn final_response_with_default_usage() -> Self {
206 Self::FinalResponse(mock_final(Usage::default()))
207 }
208
209 pub fn final_response_with_total_tokens(total_tokens: u64) -> Self {
211 Self::FinalResponse(mock_final_with_total_tokens(total_tokens))
212 }
213
214 pub fn error(message: impl Into<String>) -> Self {
216 Self::Error(MockError::provider(message))
217 }
218}
219
220#[derive(Clone, Debug)]
222pub enum MockFrame {
223 Event(MockStreamEvent),
225 Response(Box<CompletionResponse>),
227}
228
229#[derive(Debug, Default)]
233pub struct MockDocument {
234 document: Option<serde_json::Value>,
235 failed: bool,
236}
237
238impl crate::wire::document::Serves<crate::operation::Completion> for MockDocument {}
239
240impl crate::wire::document::Reassemble<MockFrame> for MockDocument {
241 fn absorb(&mut self, frame: &MockFrame) {
242 if self.document.is_some() || self.failed {
243 return;
244 }
245 match frame {
246 MockFrame::Response(response) => self.document = Some(response.raw.clone()),
247 MockFrame::Event(MockStreamEvent::FinalResponse(finish)) => {
248 self.document = serde_json::to_value(finish).ok();
249 }
250 MockFrame::Event(MockStreamEvent::Error(_)) => self.failed = true,
251 MockFrame::Event(_) => {}
252 }
253 }
254
255 fn finish(self) -> serde_json::Value {
256 self.document.unwrap_or(serde_json::Value::Null)
257 }
258}
259
260#[derive(Default)]
263pub struct MockDecoder<'id> {
264 reasoning: Vec<(String, usize)>,
266 calls: HashMap<String, usize>,
268 brand: std::marker::PhantomData<fn(&'id ()) -> &'id ()>,
269}
270
271fn id_item(id: &str) -> serde_json::Value {
273 fixture_provider_id(id).map_or(
274 serde_json::Value::Null,
275 |id| serde_json::json!({ "id": id }),
276 )
277}
278
279impl<'id> MockDecoder<'id> {
280 fn call_index(&mut self, out: &mut Out<'id, Completion>, id: &str) -> usize {
281 if let Some(index) = self.calls.get(id) {
282 return *index;
283 }
284 let index = out.fresh_index();
285 if !id.is_empty() {
286 self.calls.insert(id.to_owned(), index);
287 }
288 index
289 }
290
291 fn event(
292 &mut self,
293 event: MockStreamEvent,
294 mut out: Out<'id, Completion>,
295 ) -> Result<Flow, ProviderError> {
296 match event {
297 MockStreamEvent::Text(text) => {
298 out.run(Block::Text, &text)?;
299 }
300 MockStreamEvent::TextStart {
301 id: _,
302 additional_params,
303 } => {
304 out.end_run()?;
305 let index = out.run(Block::Text, "")?;
306 if let Some(item) = additional_params.map(fixture_item).transpose()?.flatten() {
307 out.edit(index, |slot| *slot = item)?;
308 }
309 }
310 MockStreamEvent::TextAdditionalParams(additional_params) => {
311 let Some(serde_json::Value::Object(fields)) = fixture_item(additional_params)?
313 else {
314 return Err(ProviderError::Provider(
315 "mock stream fixture `TextAdditionalParams` carries no data — \
316 drop the event instead"
317 .to_string(),
318 ));
319 };
320 let index = out.run(Block::Text, "")?;
321 out.edit(index, |item| {
322 if !item.is_object() {
323 *item = serde_json::Value::Object(serde_json::Map::new());
324 }
325 if let Some(item) = item.as_object_mut() {
326 item.extend(fields);
327 }
328 })?;
329 }
330 MockStreamEvent::ToolCall {
331 id,
332 name,
333 arguments,
334 call_id,
335 } => {
336 out.end_run()?;
337 let index = match self.calls.remove(&id) {
341 Some(index) => index,
342 None => out.fresh_index(),
343 };
344 out.fragment(
345 Some(index),
346 CallFragment {
347 id: call_id.as_deref().or(fixture_provider_id(&id)),
348 name: Some(name.as_str()),
349 ..CallFragment::default()
350 },
351 )?;
352 out.announce(index, arguments)?;
353 out.finish(index)?;
354 }
355 MockStreamEvent::ToolCallNameDelta { id, name } => {
356 out.end_run()?;
357 let index = self.call_index(&mut out, &id);
358 out.fragment(
359 Some(index),
360 CallFragment {
361 id: fixture_provider_id(&id),
362 name: Some(name.as_str()),
363 ..CallFragment::default()
364 },
365 )?;
366 }
367 MockStreamEvent::ToolCallArgumentsDelta { id, arguments } => {
368 out.end_run()?;
369 let index = self.call_index(&mut out, &id);
370 out.fragment(
371 Some(index),
372 CallFragment {
373 id: fixture_provider_id(&id),
374 arguments: Some(arguments.as_str()),
375 ..CallFragment::default()
376 },
377 )?;
378 }
379 MockStreamEvent::ToolCallEnd { id } => {
380 let index = self.call_index(&mut out, &id);
381 self.calls.remove(&id);
382 out.finish(index)?;
383 }
384 MockStreamEvent::Reasoning { id, text } => {
385 out.end_run()?;
386 match self.reasoning.iter().position(|(open, _)| *open == id) {
388 Some(at) => {
389 let (_, index) = self.reasoning.remove(at);
390 out.finish(index)?;
391 }
392 None => {
393 let index = out.fresh_index();
394 out.whole(
395 index,
396 Block::Reasoning { redacted: false },
397 id_item(&id),
398 &text,
399 )?;
400 }
401 }
402 }
403 MockStreamEvent::ReasoningDelta { id, reasoning } => {
404 out.end_run()?;
405 let index = match self.reasoning.iter().find(|(open, _)| *open == id) {
406 Some((_, index)) => *index,
407 None => {
408 let index = out.fresh_index();
409 out.open(index, Block::Reasoning { redacted: false }, id_item(&id))?;
410 self.reasoning.push((id, index));
411 index
412 }
413 };
414 out.push(index, &reasoning)?;
415 }
416 MockStreamEvent::Unknown(value) => out.unknown(value.into()),
417 MockStreamEvent::RequestId(_) => {}
418 MockStreamEvent::FinalResponse(finish) => {
419 out.end_run()?;
420 for (_, index) in std::mem::take(&mut self.reasoning) {
421 out.finish(index)?;
422 }
423 return Ok(out.end(finish));
424 }
425 MockStreamEvent::Error(error) => return Err(error.into_completion_error()),
426 }
427 Ok(Flow::More)
428 }
429
430 fn response(
431 &mut self,
432 response: CompletionResponse,
433 mut out: Out<'id, Completion>,
434 ) -> Result<Flow, ProviderError> {
435 for content in response.choice.iter().cloned() {
436 out.content(content)?;
437 }
438 Ok(out.end(Finish {
439 usage: response.usage,
440 reason: response.finish_reason(),
441 response_id: response.response_id().map(str::to_owned),
442 model: response.model().map(str::to_owned),
443 error: response.error.clone(),
444 }))
445 }
446}
447
448impl<'id> Decoder<'id, Completion, MockFrame> for MockDecoder<'id> {
449 type Event = MockFrame;
450
451 fn classify(&self, frame: MockFrame) -> WireEvent<MockFrame> {
452 WireEvent::Known(frame)
453 }
454
455 fn decode(
456 &mut self,
457 frame: MockFrame,
458 out: Out<'id, Completion>,
459 ) -> Result<Flow, ProviderError> {
460 match frame {
461 MockFrame::Event(event) => self.event(event, out),
462 MockFrame::Response(response) => self.response(*response, out),
463 }
464 }
465}