rig_core/providers/gemini/streaming/
document.rs1use serde_json::{Map, Value};
18
19use super::{GenerateContentChunk, merge_part};
20use crate::json_utils::Lenient;
21use crate::wire::document::{Reassemble, keep, slot};
22
23#[derive(Debug, Default)]
36pub struct GenerateContentResponse {
37 document: Map<String, Value>,
38 indices: Vec<u64>,
40 signatures: Vec<(u64, String)>,
43}
44
45impl GenerateContentResponse {
46 #[doc(hidden)]
50 pub fn chunk(&mut self, chunk: &Map<String, Value>) {
51 for (key, value) in chunk {
52 match (key.as_str(), value) {
53 ("candidates", Value::Array(candidates)) => {
54 for (position, candidate) in candidates.iter().enumerate() {
55 self.candidate(position, candidate);
56 }
57 }
58 ("candidates", _) => {}
61 ("promptFeedback", value) => {
62 if self.document.get(key).is_none_or(Value::is_null) {
63 self.document.insert(key.clone(), value.clone());
64 }
65 }
66 (_, value) => keep(&mut self.document, key, value),
67 }
68 }
69 }
70
71 #[doc(hidden)]
75 pub fn document(self) -> Value {
76 if self.document.is_empty() {
77 Value::Null
78 } else {
79 Value::Object(self.document)
80 }
81 }
82
83 fn candidate(&mut self, position: usize, candidate: &Value) {
85 let Value::Object(candidate) = candidate else {
86 return;
87 };
88 let index = candidate
89 .get("index")
90 .and_then(Value::as_u64)
91 .unwrap_or(position as u64);
92 let candidates = slot(&mut self.document, "candidates", Value::Array);
93 let Value::Array(candidates) = candidates else {
94 return;
95 };
96 let at = match self.indices.iter().position(|held| *held == index) {
97 Some(at) => at,
98 None => {
99 self.indices.push(index);
100 candidates.push(Value::Object(Map::new()));
101 candidates.len() - 1
102 }
103 };
104 let Some(Value::Object(held)) = candidates.get_mut(at) else {
105 return;
106 };
107 for (key, value) in candidate {
108 match (key.as_str(), value) {
109 ("content", Value::Object(content)) => {
110 if let Value::Object(held) = slot(held, "content", Value::Object) {
111 content_into(held, content, index, &mut self.signatures);
112 }
113 }
114 ("citationMetadata", Value::Object(citations)) => {
115 if let Value::Object(held) = slot(held, "citationMetadata", Value::Object) {
116 for (key, value) in citations {
117 match (key.as_str(), held.get_mut(key), value) {
118 (
119 "citationSources",
120 Some(Value::Array(sources)),
121 Value::Array(more),
122 ) => {
123 sources.extend(more.iter().cloned());
124 }
125 (_, _, value) => keep(held, key, value),
126 }
127 }
128 }
129 }
130 (_, value) => keep(held, key, value),
131 }
132 }
133 }
134}
135
136fn content_into(
138 held: &mut Map<String, Value>,
139 content: &Map<String, Value>,
140 index: u64,
141 signatures: &mut Vec<(u64, String)>,
142) {
143 for (key, value) in content {
144 let Value::Array(more) = value else {
145 keep(held, key, value);
146 continue;
147 };
148 if key != "parts" {
149 keep(held, key, value);
150 continue;
151 }
152 let Value::Array(parts) = slot(held, "parts", Value::Array) else {
153 return;
154 };
155 for part in more {
156 part_into(parts, part, index, signatures);
157 }
158 }
159}
160
161fn part_into(
164 parts: &mut Vec<Value>,
165 part: &Value,
166 index: u64,
167 signatures: &mut Vec<(u64, String)>,
168) {
169 let signature = part
170 .str("thoughtSignature")
171 .filter(|signature| !signature.is_empty())
172 .map(str::to_owned);
173 if let Some(text) = part.str("text") {
174 if text.is_empty() && signature.is_none() {
175 return;
176 }
177 let thought = part.bool("thought") == Some(true);
178 if let Some(last) = parts.last_mut()
179 && last.str("text").is_some()
180 && (last.bool("thought") == Some(true)) == thought
181 && !(signed(last) && signature.is_some())
182 {
183 merge_part(last, part);
184 return;
185 }
186 } else if let Value::Object(fields) = part {
187 let bare = ["thought", "thoughtSignature", "partMetadata"];
188 let data = fields.keys().any(|key| !bare.contains(&key.as_str()));
189 if !data && let Some(signature) = signature {
190 match parts.last_mut().and_then(Value::as_object_mut) {
191 Some(last) => {
192 last.insert("thoughtSignature".to_owned(), Value::String(signature));
193 }
194 None => signatures.push((index, signature)),
195 }
196 return;
197 }
198 }
199 let mut part = part.clone();
200 if let Some(at) = signatures.iter().position(|(held, _)| *held == index)
201 && let Some(fields) = part.as_object_mut()
202 {
203 let (_, signature) = signatures.remove(at);
204 fields
205 .entry("thoughtSignature")
206 .or_insert(Value::String(signature));
207 }
208 parts.push(part);
209}
210
211fn signed(part: &Value) -> bool {
213 part.str("thoughtSignature")
214 .is_some_and(|signature| !signature.is_empty())
215}
216
217impl crate::wire::document::Serves<crate::operation::Completion> for GenerateContentResponse {}
218
219impl Reassemble<GenerateContentChunk> for GenerateContentResponse {
220 fn absorb(&mut self, GenerateContentChunk(chunk): &GenerateContentChunk) {
221 self.chunk(chunk);
222 }
223
224 fn finish(self) -> Value {
225 self.document()
226 }
227}
228
229#[cfg(test)]
230mod tests;