1use std::borrow::Cow;
12use std::collections::BTreeMap;
13
14use serde_json::{Map, Value, json};
15
16use crate::completion::{FinishReason, Usage};
17use crate::error::ProviderError;
18use crate::json_utils::Lenient;
19use crate::message::{
20 DocumentRange, DocumentSourceKind, Image, ImageMediaType, MimeType, Source, SourceLocation,
21};
22use crate::operation::completion::merge;
23use crate::operation::{Block, CallFragment, Completion, Finish};
24use crate::providers::internal::wire;
25use crate::wire::{Decoder, Flow, Out, SpanUnit, WireCitation, WireEvent, WireFrame, WireSpan};
26
27const KNOWN_EVENT_TYPES: &[&str] = &[
29 "interaction.created",
30 "interaction.completed",
31 "interaction.status_update",
32 "step.start",
33 "step.delta",
34 "step.stop",
35 "error",
36];
37
38const INTERACTION_MARKER_KEYS: &[&str] = &["steps", "status", "usage", "object", "id"];
41
42const CURSOR_TYPE: &str = "gemini.stream_cursor";
44
45#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54pub struct StreamCursor {
55 pub interaction_id: Option<String>,
57 pub event_id: Option<String>,
59}
60
61impl StreamCursor {
62 pub fn of<E>(item: &crate::streaming::Item<E>) -> Option<Self> {
64 let crate::streaming::Item::Unknown(payload) = item else {
65 return None;
66 };
67 let payload = payload.value();
68 let field = |key: &str| payload.str(key).map(str::to_owned);
69 (payload.str("type") == Some(CURSOR_TYPE)).then(|| Self {
70 interaction_id: field("interaction_id"),
71 event_id: field("event_id"),
72 })
73 }
74
75 fn payload(&self) -> crate::streaming::UnknownPayload {
77 crate::streaming::UnknownPayload::new(json!({
78 "type": CURSOR_TYPE,
79 "interaction_id": self.interaction_id,
80 "event_id": self.event_id,
81 }))
82 }
83}
84
85pub enum InteractionsEvent {
87 Sse(SseEvent),
89 Whole(Map<String, Value>),
91}
92
93#[derive(Debug)]
95pub struct SseEvent {
96 pub event_type: String,
98 pub fields: Map<String, Value>,
100}
101
102impl<'de> serde::Deserialize<'de> for SseEvent {
105 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
106 let mut fields = Map::deserialize(deserializer)?;
107 match fields.shift_remove("event_type") {
108 Some(Value::String(event_type)) => Ok(Self { event_type, fields }),
109 Some(_) => Err(serde::de::Error::custom("`event_type` is not a string")),
110 None => Err(serde::de::Error::missing_field("event_type")),
111 }
112 }
113}
114
115#[derive(Default)]
125pub struct InteractionsDecoder {
126 steps: BTreeMap<usize, Open>,
128 model: Option<String>,
131 status: Option<String>,
133 interaction_id: Option<String>,
135}
136
137enum Open {
139 Thought,
140 Output(Value, Vec<(usize, Value)>),
143 Call(String),
145 Opaque,
146}
147
148pub fn usage_of(usage: &Value) -> Usage {
153 let tool_use = usage.u64("total_tool_use_tokens");
154 let thoughts = usage.u64("total_thought_tokens");
155 let add = |key: &str, more: Option<u64>| usage.u64(key).map(|count| count + more.unwrap_or(0));
156 let (input_tokens, output_tokens) = (
157 add("total_input_tokens", tool_use),
158 add("total_output_tokens", thoughts),
159 );
160 Usage {
161 input_tokens,
162 output_tokens,
163 cached_input_tokens: usage.u64("total_cached_tokens"),
164 reasoning_tokens: thoughts,
165 tool_use_prompt_tokens: tool_use,
166 total_tokens: input_tokens
167 .zip(output_tokens)
168 .map(|(input, output)| input + output),
169 cache_creation_input_tokens: None,
170 cost: None,
171 }
172}
173
174fn image_of(content: &Value) -> Option<Image> {
176 let data = match (content.str("data"), content.str("uri")) {
177 (Some(data), _) => DocumentSourceKind::Base64(data.to_owned()),
178 (None, Some(uri)) => DocumentSourceKind::Url(uri.to_owned()),
179 (None, None) => return None,
180 };
181 Some(Image {
182 data,
183 media_type: content
184 .str("mime_type")
185 .and_then(ImageMediaType::from_mime_type),
186 detail: None,
187 native: None,
188 })
189}
190
191fn open_content(
193 content: Value,
194 out: &mut Out<'_, Completion>,
195) -> Result<(usize, Value), ProviderError> {
196 let index = out.fresh_index();
197 let block = match content.str("type") {
198 Some("text") => Block::Text,
199 Some("image") => image_of(&content).map_or(Block::Opaque { replay: true }, Block::Image),
200 _ => Block::Opaque { replay: true },
201 };
202 let text = matches!(block, Block::Text);
203 out.open(index, block, Value::Null)?;
204 if let Some(fragment) = content.str("text").filter(|_| text) {
205 out.push(index, fragment)?;
206 }
207 Ok((index, content))
208}
209
210impl InteractionsDecoder {
211 pub fn resuming(interaction_id: impl Into<String>) -> Self {
213 Self {
214 interaction_id: Some(interaction_id.into()),
215 ..Self::default()
216 }
217 }
218
219 fn start(
222 &mut self,
223 index: usize,
224 step: Value,
225 out: &mut Out<'_, Completion>,
226 ) -> Result<(), ProviderError> {
227 let open = match step.str("type") {
228 Some("thought") => {
229 let summary: Vec<String> = step
230 .arr("summary")
231 .iter()
232 .filter(|summary| summary.str("type") == Some("text"))
233 .filter_map(|summary| summary.str("text").map(str::to_owned))
234 .collect();
235 out.open(index, Block::Reasoning { redacted: false }, step)?;
236 for text in summary {
237 out.push(index, &text)?;
238 }
239 Open::Thought
240 }
241 Some("model_output") => {
242 let mut step = step;
243 let content = step
244 .as_object_mut()
245 .and_then(|step| step.insert("content".to_owned(), Value::Array(Vec::new())));
246 let items = match content {
247 Some(Value::Array(items)) => items,
248 _ => Vec::new(),
249 };
250 let items = items.into_iter().map(|content| open_content(content, out));
251 Open::Output(step, items.collect::<Result<_, _>>()?)
252 }
253 Some("function_call") => {
254 call_fields(index, &step, out)?;
255 out.edit(index, |slot| *slot = step)?;
256 Open::Call(String::new())
257 }
258 kind => {
261 let replay = !matches!(kind, Some("user_input" | "function_result"));
262 out.open(index, Block::Opaque { replay }, step)?;
263 Open::Opaque
264 }
265 };
266 self.steps.insert(index, open);
267 Ok(())
268 }
269
270 fn delta(
274 &mut self,
275 index: usize,
276 mut delta: Map<String, Value>,
277 out: &mut Out<'_, Completion>,
278 ) -> Result<(), ProviderError> {
279 let kind = delta
280 .get("type")
281 .and_then(Value::as_str)
282 .unwrap_or_default();
283 if !self.steps.contains_key(&index) {
284 let step = match kind {
285 "text" | "text_annotation_delta" | "image" | "audio" | "document" | "video" => {
286 "model_output"
287 }
288 "thought_summary" | "thought_signature" => "thought",
289 "arguments_delta" => "function_call",
290 other => other,
291 };
292 self.start(index, json!({ "type": step }), out)?;
293 }
294 let Some(open) = self.steps.get_mut(&index) else {
295 return Ok(());
296 };
297 match (open, kind) {
298 (Open::Output(_, items), _) => match items.last_mut() {
301 Some((block, item))
302 if matches!(kind, "text" | "text_annotation_delta")
303 && item.str("type") == Some("text") =>
304 {
305 if let Some(fragment) = delta.get("text").and_then(Value::as_str)
306 && kind == "text"
307 {
308 out.push(*block, fragment)?;
309 }
310 merge(item, &delta);
311 Ok(())
312 }
313 _ => {
314 items.push(open_content(Value::Object(delta), out)?);
315 Ok(())
316 }
317 },
318 (Open::Thought, "thought_summary") => {
319 let content = delta.shift_remove("content").unwrap_or_default();
320 if let Some(text) = content
321 .str("text")
322 .filter(|_| content.str("type") == Some("text"))
323 {
324 out.push(index, text)?;
325 }
326 let summary = Value::Array(vec![content]);
327 out.edit(index, |item| {
328 merge(item, &Map::from_iter([("summary".to_owned(), summary)]))
329 })
330 }
331 (Open::Call(arguments), "arguments_delta") => {
332 let fragment = match delta.get("arguments") {
333 Some(Value::String(fragment)) => Cow::Borrowed(fragment.as_str()),
334 Some(Value::Null) | None => Cow::Borrowed(""),
335 Some(other) => Cow::Owned(other.to_string()),
336 };
337 arguments.push_str(&fragment);
338 let fragment = CallFragment {
339 arguments: Some(&fragment),
340 ..CallFragment::default()
341 };
342 out.fragment(Some(index), fragment)
343 }
344 (open, _) => {
345 if matches!(open, Open::Call(_)) && kind == "function_call" {
346 call_fields(index, &Value::Object(delta.clone()), out)?;
347 }
348 out.edit(index, |item| {
351 if item.get("type") != delta.get("type") {
352 merge(item, &delta);
353 } else if let Some(item) = item.as_object_mut() {
354 item.extend(delta);
355 }
356 })
357 }
358 }
359 }
360
361 fn stop(&mut self, index: usize, out: &mut Out<'_, Completion>) -> Result<(), ProviderError> {
363 match self.steps.remove(&index) {
364 None => Ok(()),
365 Some(Open::Call(arguments)) => {
366 if !arguments.is_empty()
369 && let Ok(arguments) = crate::json_utils::parse_tool_arguments(&arguments)
370 {
371 out.edit(index, |item| {
372 if let Some(item) = item.as_object_mut() {
373 item.insert("arguments".to_owned(), arguments);
374 }
375 })?;
376 }
377 out.finish(index)
378 }
379 Some(Open::Output(step, items)) if items.is_empty() => {
381 let block = out.fresh_index();
382 out.open(block, Block::Text, step)?;
383 out.finish(block)
384 }
385 Some(Open::Output(step, items)) => {
386 for (block, item) in items {
387 for annotation in item.arr("annotations") {
388 if let Some(citation) = annotation_citation(annotation) {
389 out.cite(block, citation);
390 }
391 }
392 let mut step = step.clone();
393 if let Some(step) = step.as_object_mut() {
394 step.insert("content".to_owned(), Value::Array(vec![item]));
395 }
396 out.edit(block, |slot| *slot = step)?;
397 out.finish(block)?;
398 }
399 Ok(())
400 }
401 Some(Open::Thought | Open::Opaque) => out.finish(index),
402 }
403 }
404
405 fn complete(&mut self, interaction: Map<String, Value>, out: Out<'_, Completion>) -> Flow {
407 let interaction = Value::Object(interaction);
408 let field = |key: &str| interaction.str(key).map(str::to_owned);
409 let model = field("model")
411 .or_else(|| field("agent"))
412 .or_else(|| self.model.take());
413 let status = field("status").or_else(|| self.status.take());
414 let (reason, error) = ending(status.as_deref(), interaction.arr("errors"));
415 let finish = Finish {
416 usage: usage_of(interaction.get("usage").unwrap_or(&Value::Null)),
417 reason,
418 response_id: field("id"),
419 model,
420 error,
421 };
422 out.end(finish)
423 }
424}
425
426fn ending(status: Option<&str>, errors: &[Value]) -> (Option<FinishReason>, Option<String>) {
434 let messages: Vec<&str> = errors
435 .iter()
436 .filter_map(|error| error.str("message"))
437 .collect();
438 let detail = (!messages.is_empty()).then(|| format!(": {}", messages.join("; ")));
439 let detail = detail.unwrap_or_default();
440 let other = |status: &str| Some(FinishReason::Other(status.to_owned()));
441 match status {
442 Some("completed") => (Some(FinishReason::Stop), None),
443 Some("requires_action") => (Some(FinishReason::ToolCalls), None),
444 Some("incomplete" | "budget_exceeded") => (Some(FinishReason::Length), None),
445 Some(status @ ("failed" | "cancelled")) => (
446 other(status),
447 Some(format!("The interaction {status}{detail}")),
448 ),
449 Some(status @ ("in_progress" | "queued")) => (
450 other(status),
451 Some(format!("The interaction was read while {status}")),
452 ),
453 Some(status) => (other(status), None),
454 None => (None, Some("The interaction states no status".to_owned())),
455 }
456}
457
458fn annotation_citation(annotation: &Value) -> Option<WireCitation> {
463 let field = |key: &str| annotation.str(key).map(str::to_owned);
464 let (location, title) = match annotation.str("type")? {
465 "url_citation" => (SourceLocation::Url { url: field("url")? }, field("title")),
466 "file_citation" => {
467 let page = annotation
468 .u64("page_number")
469 .and_then(|page| u32::try_from(page).ok());
470 let within = page.map(|page| DocumentRange::Pages(page..page.saturating_add(1)));
471 let id = field("document_uri");
472 (
473 SourceLocation::Document {
474 index: None,
475 id,
476 within,
477 },
478 field("file_name"),
479 )
480 }
481 "place_citation" => {
482 let location = match (field("url"), field("place_id")) {
483 (Some(url), _) => SourceLocation::Url { url },
484 (None, Some(id)) => SourceLocation::Document {
485 index: None,
486 id: Some(id),
487 within: None,
488 },
489 (None, None) => return None,
490 };
491 (location, field("name"))
492 }
493 _ => return None,
494 };
495 let mut source = Source::new(location);
496 if let Some(title) = title {
497 source = source.title(title);
498 }
499 let span = annotation.u64("end_index").map(|end| {
501 WireSpan::new(
502 annotation.u64("start_index").unwrap_or(0),
503 end,
504 SpanUnit::Bytes,
505 )
506 });
507 Some(WireCitation::new(span, vec![source]))
508}
509
510fn call_fields(
512 index: usize,
513 step: &Value,
514 out: &mut Out<'_, Completion>,
515) -> Result<(), ProviderError> {
516 let (id, name) = (step.str("id"), step.str("name"));
517 out.fragment(
518 Some(index),
519 CallFragment {
520 id,
521 name,
522 arguments: None,
523 },
524 )?;
525 step.get("arguments")
526 .map_or(Ok(()), |arguments| out.announce(index, arguments.clone()))
527}
528
529impl<'id> Decoder<'id, Completion> for InteractionsDecoder {
532 type Event = InteractionsEvent;
533
534 fn classify(&self, frame: WireFrame) -> WireEvent<InteractionsEvent> {
538 wire::classify_or_untagged(
539 &frame.as_str(),
540 "event_type",
541 |tag| KNOWN_EVENT_TYPES.contains(&tag),
542 InteractionsEvent::Sse,
543 |data| {
544 wire::classify_marker_keyed_frame(data, INTERACTION_MARKER_KEYS)
545 .map(InteractionsEvent::Whole)
546 },
547 )
548 }
549
550 fn decode(
551 &mut self,
552 event: InteractionsEvent,
553 mut out: Out<'id, Completion>,
554 ) -> Result<Flow, ProviderError> {
555 let SseEvent {
556 event_type,
557 fields: event,
558 } = match event {
559 InteractionsEvent::Sse(event) => event,
560 InteractionsEvent::Whole(mut interaction) => {
561 let steps = match interaction.shift_remove("steps") {
562 Some(Value::Array(steps)) => steps,
563 _ => Vec::new(),
564 };
565 for (index, step) in steps.into_iter().enumerate() {
566 self.start(index, step, &mut out)?;
567 self.stop(index, &mut out)?;
568 }
569 return Ok(self.complete(interaction, out));
570 }
571 };
572 let mut event = Value::Object(event);
573 let index = |event: &Value| {
574 event
575 .u64("index")
576 .and_then(|index| usize::try_from(index).ok())
577 .ok_or_else(|| {
578 ProviderError::Response(format!(
579 "an Interactions `{event_type}` event names no step index"
580 ))
581 })
582 };
583 let malformed = |field: &str| {
584 ProviderError::Response(format!(
585 "an Interactions `{event_type}` event carries no `{field}` object"
586 ))
587 };
588 let event_id = event.str("event_id").map(str::to_owned);
589 let resumable = event_id.is_some() || event_type == "interaction.created";
590 match event_type.as_str() {
591 "step.start" => match event.get_mut("step").map(Value::take) {
592 None => {}
594 Some(step @ Value::Object(_)) => self.start(index(&event)?, step, &mut out)?,
595 Some(_) => return Err(malformed("step")),
596 },
597 "step.delta" => match event.get_mut("delta").map(Value::take) {
598 Some(Value::Object(delta)) => self.delta(index(&event)?, delta, &mut out)?,
599 _ => return Err(malformed("delta")),
600 },
601 "step.stop" => self.stop(index(&event)?, &mut out)?,
602 "interaction.created" => {
603 let field = |key: &str| {
604 event
605 .at(&format!("/interaction/{key}"))
606 .and_then(Value::as_str)
607 .map(str::to_owned)
608 };
609 self.model = field("model").or_else(|| field("agent"));
610 self.status = field("status");
611 if let Some(id) = field("id") {
612 self.interaction_id = Some(id);
613 }
614 }
615 "interaction.status_update" => {
616 if let Some(status) = event.str("status") {
617 self.status = Some(status.to_owned());
618 }
619 }
620 "interaction.completed" => {
621 let interaction = match event.get_mut("interaction").map(Value::take) {
622 Some(Value::Object(interaction)) => interaction,
623 _ => Map::new(),
624 };
625 return Ok(self.complete(interaction, out));
626 }
627 "error" => {
628 let Value::Object(mut body) = event else {
629 return Err(ProviderError::from_provider_body(event_type));
630 };
631 body.insert("event_type".to_owned(), Value::from(event_type));
632 return Err(ProviderError::from_provider_body(
633 Value::Object(body).to_string(),
634 ));
635 }
636 _ => {}
637 }
638 if resumable {
639 let cursor = StreamCursor {
640 interaction_id: self.interaction_id.clone(),
641 event_id,
642 };
643 out.unknown(cursor.payload());
644 }
645 Ok(Flow::More)
646 }
647}
648
649pub mod document;
650
651#[cfg(test)]
652mod tests;