1use std::borrow::Cow;
16use std::collections::{HashMap, HashSet};
17
18use serde_json::{Value, json};
19
20use crate::completion::{Cost, FinishReason, Usage};
21use crate::error::ProviderError;
22use crate::json_utils::Lenient;
23use crate::message::{Source, SourceLocation};
24use crate::operation::{Block, CallFragment, Completion, Finish};
25use crate::providers::internal::wire;
26use crate::wire::{Decoder, Flow, Out, WireCitation, WireEvent, WireFrame};
27
28fn is_known_responses_event_type(kind: &str) -> bool {
32 matches!(kind, "error" | "keepalive")
33 || is_lifecycle_event(kind)
34 || matches!(
35 kind.strip_prefix("response."),
36 Some(
37 "output_item.added"
38 | "output_item.done"
39 | "content_part.added"
40 | "content_part.done"
41 | "output_text.delta"
42 | "output_text.done"
43 | "refusal.delta"
44 | "refusal.done"
45 | "function_call_arguments.delta"
46 | "function_call_arguments.done"
47 | "custom_tool_call_input.delta"
48 | "custom_tool_call_input.done"
49 | "reasoning_summary_part.added"
50 | "reasoning_summary_part.done"
51 | "reasoning_summary_text.delta"
52 | "reasoning_summary_text.done"
53 | "reasoning_text.delta"
54 | "reasoning_text.done"
55 )
56 )
57}
58
59pub(crate) fn is_lifecycle_event(kind: &str) -> bool {
62 matches!(
63 kind.strip_prefix("response."),
64 Some("created" | "queued" | "in_progress" | "completed" | "failed" | "incomplete")
65 )
66}
67
68#[derive(Debug)]
70pub enum ResponsesEvent {
71 Frame {
73 kind: String,
75 frame: Value,
77 raw: Option<String>,
80 },
81 Whole(Value),
84 Failure(String),
87 Sentinel,
89}
90
91const WHOLE_BODY_MARKERS: &[&str] = &["object", "output", "status", "id", "error"];
94
95struct Tagged(Value);
97
98impl<'de> serde::Deserialize<'de> for Tagged {
99 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100 where
101 D: serde::Deserializer<'de>,
102 {
103 let value = Value::deserialize(deserializer)?;
104 if value.str("type").is_some() {
105 Ok(Self(value))
106 } else {
107 Err(serde::de::Error::custom("the payload names no `type`"))
108 }
109 }
110}
111
112pub fn classify_responses_payload(data: &str) -> WireEvent<ResponsesEvent> {
115 if data.trim() == "[DONE]" {
116 return WireEvent::Known(ResponsesEvent::Sentinel);
117 }
118 wire::classify_or_untagged(
119 data,
120 "type",
121 is_known_responses_event_type,
122 |Tagged(frame)| match frame.str("type") {
123 Some("error") => ResponsesEvent::Failure(data.to_owned()),
124 kind => ResponsesEvent::Frame {
125 raw: (kind == Some("response.failed")).then(|| data.to_owned()),
126 kind: kind.unwrap_or_default().to_owned(),
127 frame,
128 },
129 },
130 |data| {
131 wire::classify_marker_keyed_frame::<Value>(data, WHOLE_BODY_MARKERS).map(|body| {
132 let error = body.get("error").is_some_and(|error| !error.is_null());
133 if error && body.get("output").is_none() && body.get("status").is_none() {
134 ResponsesEvent::Failure(data.to_owned())
135 } else {
136 ResponsesEvent::Whole(body)
137 }
138 })
139 },
140 )
141}
142
143#[derive(Clone, Copy, PartialEq, Eq, Debug)]
145enum Kind {
146 Message,
147 Reasoning,
148 Call,
149 Opaque,
150}
151
152impl Kind {
153 fn of(item: &Value) -> Self {
154 match item.str("type") {
155 Some("message") => Self::Message,
156 Some("reasoning") => Self::Reasoning,
157 Some("function_call" | "custom_tool_call") => Self::Call,
158 _ => Self::Opaque,
159 }
160 }
161}
162
163struct Slot {
165 at: usize,
167 kind: Kind,
168 id: Option<String>,
170 open: bool,
171 text: String,
175 field: Option<&'static str>,
176 part: u64,
177 arguments: String,
179 custom: bool,
180 named: bool,
182 titled: bool,
184}
185
186const CLIENT_EXECUTED: &[&str] = &[
189 "computer_call",
190 "local_shell_call",
191 "shell_call",
192 "apply_patch_call",
193 "mcp_approval_request",
194];
195
196fn item_id(item: &Value) -> Option<&str> {
197 item.str("id").filter(|id| !id.is_empty())
198}
199
200fn arguments_of(item: &Value) -> Option<String> {
203 if item.str("type") == Some("custom_tool_call") {
204 return item
205 .str("input")
206 .map(|input| json!({ "input": input }).to_string());
207 }
208 match item.get("arguments")? {
209 Value::String(arguments) if arguments.is_empty() => None,
210 Value::String(arguments) => Some(arguments.clone()),
211 Value::Null => None,
212 arguments => Some(arguments.to_string()),
213 }
214}
215
216fn text_of(item: &Value) -> String {
219 let texts = |key: &str| -> Vec<&str> {
220 match item.get(key) {
221 Some(Value::String(text)) => vec![text.as_str()],
222 _ => item
223 .arr(key)
224 .iter()
225 .filter_map(|part| {
226 part.as_str()
227 .or_else(|| part.str("text"))
228 .or_else(|| part.str("refusal"))
229 })
230 .collect(),
231 }
232 };
233 match Kind::of(item) {
234 Kind::Message => texts("content").concat(),
235 Kind::Reasoning => Some(texts("summary"))
236 .filter(|summary| !summary.is_empty())
237 .unwrap_or_else(|| texts("content"))
238 .join("\n\n"),
239 Kind::Call | Kind::Opaque => String::new(),
240 }
241}
242
243fn stating(mut item: Value, kind: Kind, text: &str) -> Value {
246 let (key, part) = match kind {
247 Kind::Message => (
248 "content",
249 json!({"type": "output_text", "text": text, "annotations": []}),
250 ),
251 _ => ("summary", json!({"type": "summary_text", "text": text})),
252 };
253 if let Some(fields) = item.as_object_mut() {
254 fields.insert(key.to_owned(), json!([part]));
255 }
256 item
257}
258
259fn provider_message(response: &Value, fallback: &str) -> String {
261 let parts: Vec<&str> = ["/error/code", "/error/message"]
262 .iter()
263 .filter_map(|pointer| response.at(pointer).and_then(Value::as_str))
264 .collect();
265 if parts.is_empty() {
266 fallback.to_owned()
267 } else {
268 parts.join(": ")
269 }
270}
271
272pub(crate) fn finish_reason_of(response: &Value) -> (FinishReason, Option<String>) {
277 let reason = response
278 .at("/incomplete_details/reason")
279 .and_then(Value::as_str)
280 .filter(|reason| !reason.is_empty());
281 let other = |reason: &str, error: String| (FinishReason::Other(reason.to_owned()), Some(error));
282 match response.str("status") {
283 None | Some("completed") => (FinishReason::Stop, None),
284 Some("incomplete") => match reason {
285 Some("max_output_tokens") => (FinishReason::Length, None),
286 Some("content_filter") => (FinishReason::ContentFilter, None),
287 Some(reason) => other(
288 &format!("incomplete: {reason}"),
289 format!("Response incomplete: {reason}"),
290 ),
291 None => other(
292 "incomplete",
293 "Response incomplete without a provider reason".to_owned(),
294 ),
295 },
296 Some(status @ ("failed" | "cancelled")) => other(
297 status,
298 provider_message(response, &format!("Response {status}")),
299 ),
300 Some(status @ ("queued" | "in_progress")) => {
301 other(status, format!("Response ended while {status}"))
302 }
303 Some(status) => other(
304 status,
305 format!("Response ended with the unknown status `{status}`"),
306 ),
307 }
308}
309
310pub(crate) fn usage_of(usage: &Value) -> Usage {
313 let count = |pointer: &str| usage.at(pointer).and_then(Lenient::as_u64_lenient);
314 Usage {
315 input_tokens: count("/input_tokens"),
316 output_tokens: count("/output_tokens"),
317 total_tokens: count("/total_tokens"),
318 cached_input_tokens: count("/input_tokens_details/cached_tokens"),
319 cache_creation_input_tokens: count("/input_tokens_details/cache_write_tokens"),
320 reasoning_tokens: count("/output_tokens_details/reasoning_tokens"),
321 ..Usage::default()
322 }
323 .cost(reported_cost(usage))
324}
325
326const TICKS_PER_USD: f64 = 1e10;
328
329fn reported_cost(usage: &Value) -> Option<Cost> {
332 let total = match usage.u64("cost_in_usd_ticks") {
333 Some(ticks) => ticks as f64 / TICKS_PER_USD,
334 None => usage.f64("cost").filter(|cost| cost.is_finite())?,
335 };
336 Some(Cost::from_total(total))
337}
338
339fn citations_of(item: &Value) -> Vec<WireCitation> {
344 item.arr("content")
345 .iter()
346 .flat_map(|part| part.arr("annotations"))
347 .filter_map(source_of)
348 .map(|source| WireCitation::new(None, vec![source]))
349 .collect()
350}
351
352fn source_of(annotation: &Value) -> Option<Source> {
355 let text = |key: &str| {
356 annotation
357 .str(key)
358 .filter(|text| !text.is_empty())
359 .map(str::to_owned)
360 };
361 let location = match annotation.str("type")? {
362 "url_citation" => SourceLocation::Url { url: text("url")? },
363 "file_citation" | "file_path" | "container_file_citation" => SourceLocation::File {
364 file_id: text("file_id")?,
365 filename: text("filename"),
366 container_id: text("container_id"),
367 },
368 _ => return None,
369 };
370 let source = Source::new(location);
371 Some(match text("title") {
372 Some(title) => source.title(title),
373 None => source,
374 })
375}
376
377#[derive(Default)]
391pub struct ResponsesDecoder {
392 slots: Vec<Slot>,
394 indexed: HashMap<usize, usize>,
396 current: Option<usize>,
398 held: Vec<usize>,
401}
402
403impl ResponsesDecoder {
404 pub fn new() -> Self {
406 Self::default()
407 }
408
409 fn addressed(&self, frame: &Value, kind: Kind) -> Result<Option<usize>, ProviderError> {
415 let index = output_index(frame)?;
416 if let Some(slot) = index.and_then(|index| self.indexed.get(&index)) {
417 return Ok(Some(*slot));
418 }
419 let id = frame
420 .at("/item/id")
421 .and_then(Value::as_str)
422 .or_else(|| frame.str("item_id"));
423 if let Some(slot) = id.filter(|id| !id.is_empty()).and_then(|id| {
424 self.slots
425 .iter()
426 .rposition(|slot| slot.id.as_deref() == Some(id))
427 }) {
428 return Ok(Some(slot));
429 }
430 Ok(self.current.filter(|current| {
431 (index.is_none() || !self.indexed.values().any(|slot| slot == current))
432 && self
433 .slots
434 .get(*current)
435 .is_some_and(|slot| slot.open && slot.kind == kind)
436 }))
437 }
438
439 fn added(
442 &mut self,
443 index: Option<usize>,
444 item: &Value,
445 out: &mut Out<'_, Completion>,
446 ) -> Result<usize, ProviderError> {
447 if let Some(previous) = index.and_then(|index| self.indexed.get(&index).copied()) {
448 self.vacate(previous, out)?;
449 }
450 let at = match index {
451 Some(index) if !self.slots.iter().any(|slot| slot.at == index) => index,
452 _ => out.fresh_index(),
453 };
454 let kind = Kind::of(item);
455 let custom = item.str("type") == Some("custom_tool_call");
456 match kind {
457 Kind::Message => out.open(at, Block::Text, Value::Null)?,
458 Kind::Reasoning => out.open(at, Block::Reasoning { redacted: false }, Value::Null)?,
459 Kind::Call => out.fragment(
460 Some(at),
461 CallFragment {
462 id: item.str("call_id"),
463 name: item.str("name"),
464 arguments: None,
465 },
466 )?,
467 Kind::Opaque => {
469 let replay = item
470 .str("type")
471 .is_some_and(|kind| !CLIENT_EXECUTED.contains(&kind));
472 out.open(at, Block::Opaque { replay }, item.clone())?;
473 }
474 }
475 self.slots.push(Slot {
476 at,
477 kind,
478 id: item_id(item).map(str::to_owned),
479 open: true,
480 text: String::new(),
481 field: None,
482 part: 0,
483 arguments: String::new(),
484 custom,
485 named: item.str("call_id").is_some_and(|id| !id.is_empty()),
486 titled: item.str("name").is_some_and(|name| !name.is_empty()),
487 });
488 let slot = self.slots.len() - 1;
489 if let Some(index) = index {
490 self.indexed.insert(index, slot);
491 }
492 self.current = Some(slot);
493 if let Some(call) = self
494 .slots
495 .get_mut(slot)
496 .filter(|slot| slot.kind == Kind::Call)
497 {
498 let arguments = item.str(if custom { "input" } else { "arguments" });
499 send(call, arguments.unwrap_or_default(), out)?;
500 }
501 Ok(slot)
502 }
503
504 fn vacate(&mut self, slot: usize, out: &mut Out<'_, Completion>) -> Result<(), ProviderError> {
507 let Some(open) = self.slots.get_mut(slot).filter(|slot| slot.open) else {
508 return Ok(());
509 };
510 open.open = false;
511 let at = open.at;
512 if open.kind == Kind::Call {
513 flush(open, out)?;
514 }
515 out.close(at)?;
516 self.release(false, out)
517 }
518
519 fn release(
522 &mut self,
523 complete: bool,
524 out: &mut Out<'_, Completion>,
525 ) -> Result<(), ProviderError> {
526 std::mem::take(&mut self.held)
527 .into_iter()
528 .try_for_each(|at| {
529 if complete {
530 out.finish(at)
531 } else {
532 out.close(at)
533 }
534 })
535 }
536
537 fn text(
540 &mut self,
541 frame: &Value,
542 field: &'static str,
543 part: &str,
544 out: &mut Out<'_, Completion>,
545 ) -> Result<(), ProviderError> {
546 let delta = frame.str("delta").unwrap_or_default();
547 if delta.is_empty() {
548 return Ok(());
549 }
550 let part = frame.u64(part).unwrap_or(0);
551 let kind = match field {
552 "message" => Kind::Message,
553 _ => Kind::Reasoning,
554 };
555 let known = self.addressed(frame, kind)?.and_then(|at| {
556 let slot = self.slots.get(at)?;
557 Some((at, slot.open, slot.kind == kind))
558 });
559 let slot = match known {
560 Some((slot, true, true)) => slot,
561 Some((_, false, true)) => return Ok(()),
564 _ => self.added(
565 output_index(frame)?,
566 &json!({"type": if kind == Kind::Message { "message" } else { "reasoning" }}),
567 out,
568 )?,
569 };
570 let Some(streamed) = self.slots.get_mut(slot).filter(|slot| slot.open) else {
571 return Ok(());
572 };
573 if streamed.field.is_some_and(|known| known != field) {
574 return Ok(());
575 }
576 if streamed.part != part && kind != Kind::Message && !streamed.text.is_empty() {
577 streamed.text.push_str("\n\n");
578 out.push(streamed.at, "\n\n")?;
579 }
580 streamed.field = Some(field);
581 streamed.part = part;
582 streamed.text.push_str(delta);
583 out.push(streamed.at, delta)
584 }
585
586 fn arguments(
589 &mut self,
590 frame: &Value,
591 out: &mut Out<'_, Completion>,
592 ) -> Result<(), ProviderError> {
593 let slot = self.addressed(frame, Kind::Call)?;
594 if let Some(call) = slot
595 .and_then(|slot| self.slots.get_mut(slot))
596 .filter(|slot| slot.open && slot.kind == Kind::Call)
597 {
598 send(call, frame.str("delta").unwrap_or_default(), out)?;
599 }
600 Ok(())
601 }
602
603 fn done(
606 &mut self,
607 slot: usize,
608 item: Value,
609 out: &mut Out<'_, Completion>,
610 ) -> Result<(), ProviderError> {
611 let Some(done) = self.slots.get_mut(slot) else {
612 return Ok(());
613 };
614 done.open = false;
615 if done.id.is_none() {
616 done.id = item_id(&item).map(str::to_owned);
617 }
618 let at = done.at;
619 let mut item = item;
620 let mut nameless = false;
621 match done.kind {
622 Kind::Call => {
623 nameless = !done.titled && item.str("name").is_none_or(str::is_empty);
624 let arguments = arguments_of(&item).unwrap_or_else(|| streamed_arguments(done));
625 let rest = remainder(done, &arguments, out)?;
626 out.fragment(
627 Some(at),
628 CallFragment {
629 id: item.str("call_id").filter(|_| !done.named),
630 name: item.str("name"),
631 arguments: Some(&rest),
632 },
633 )?;
634 }
635 Kind::Message | Kind::Reasoning => {
636 let text = text_of(&item);
637 if text.is_empty() {
638 if !done.text.is_empty() && done.field != Some("reasoning") {
642 item = stating(item, done.kind, &done.text);
643 }
644 } else if let Some(rest) = text.strip_prefix(done.text.as_str()) {
645 out.push(at, rest)?;
646 } else {
647 out.restate(at, &text)?;
649 }
650 let citations = citations_of(&item);
651 if done.kind == Kind::Message && !citations.is_empty() {
652 out.set_citations(at, citations);
655 }
656 }
657 Kind::Opaque => {}
658 }
659 let reasoning = done.kind == Kind::Reasoning;
660 out.edit(at, |native| *native = item)?;
661 if reasoning {
662 self.held.push(at);
665 return Ok(());
666 }
667 out.finish(at)?;
668 self.release(!nameless, out)
671 }
672
673 fn item_event(
675 &mut self,
676 kind: &str,
677 mut frame: Value,
678 out: &mut Out<'_, Completion>,
679 ) -> Result<(), ProviderError> {
680 match kind {
681 "response.output_item.added" | "response.output_item.done" => {
682 let Some(item) = frame.get("item").filter(|item| item.is_object()) else {
683 return Ok(());
684 };
685 let index = output_index(&frame)?;
686 if kind == "response.output_item.added" {
687 return self.added(index, item, out).map(|_| ());
688 }
689 let known = self.addressed(&frame, Kind::of(item))?;
690 let restates = |slot: &Slot| {
693 slot.kind == Kind::of(item)
694 && (index.is_some()
695 || item_id(item)
696 .zip(slot.id.as_deref())
697 .is_none_or(|(id, known)| id == known))
698 };
699 let known = known.and_then(|at| {
700 let slot = self.slots.get(at)?;
701 let same = item_id(item).is_some() && slot.id.as_deref() == item_id(item);
702 Some((at, slot.open && restates(slot), same))
703 });
704 let slot = match known {
705 Some((slot, true, _)) => slot,
706 Some((_, false, true)) => return Ok(()),
708 _ => self.added(index, item, out)?,
709 };
710 let item = frame
711 .as_object_mut()
712 .and_then(|frame| frame.shift_remove("item"))
713 .unwrap_or_default();
714 self.done(slot, item, out)
715 }
716 "response.output_text.delta" | "response.refusal.delta" => {
718 self.text(&frame, "message", "content_index", out)
719 }
720 "response.reasoning_summary_text.delta" => {
721 self.text(&frame, "summary", "summary_index", out)
722 }
723 "response.reasoning_text.delta" => self.text(&frame, "reasoning", "content_index", out),
724 "response.function_call_arguments.delta" | "response.custom_tool_call_input.delta" => {
725 self.arguments(&frame, out)
726 }
727 _ => Ok(()),
728 }
729 }
730
731 fn restated(
739 &self,
740 index: usize,
741 item: &Value,
742 kinds: &[Kind],
743 taken: &HashSet<usize>,
744 ) -> Option<usize> {
745 let id = item_id(item);
746 let free = |at: &usize| {
747 !taken.contains(at)
748 && self
749 .slots
750 .get(*at)
751 .is_some_and(|slot| slot.kind == Kind::of(item))
752 };
753 let find = |fits: &dyn Fn(usize, &Slot) -> bool| {
754 self.slots
755 .iter()
756 .enumerate()
757 .find(|(at, slot)| free(at) && fits(*at, slot))
758 .map(|(at, _)| at)
759 };
760 id.and_then(|id| find(&|_, slot| slot.id.as_deref() == Some(id)))
761 .or_else(|| self.indexed.get(&index).copied().filter(free))
762 .or_else(|| {
763 find(&|at, slot| {
764 !self.indexed.values().any(|indexed| *indexed == at)
765 && (id.is_none() || slot.id.is_none())
766 })
767 })
768 .or_else(|| {
769 find(&|at, slot| {
770 (id.is_none() || slot.id.is_none())
771 && self.indexed.iter().any(|(streamed, indexed)| {
772 *indexed == at
773 && kinds.get(*streamed).is_none_or(|other| *other != slot.kind)
774 })
775 })
776 })
777 }
778
779 fn finish(
785 &mut self,
786 mut response: Value,
787 mut out: Out<'_, Completion>,
788 ) -> Result<Flow, ProviderError> {
789 let output = match response.get_mut("output") {
790 Some(Value::Array(output)) => std::mem::take(output),
791 _ => Vec::new(),
792 };
793 if self.slots.is_empty()
794 && !output.iter().any(|item| Kind::of(item) == Kind::Reasoning)
795 && let Some(reasoning) = response.str("reasoning").filter(|text| !text.is_empty())
796 {
797 out.whole(
800 0,
801 Block::Reasoning { redacted: false },
802 Value::Null,
803 reasoning,
804 )?;
805 }
806 let mut taken = HashSet::new();
807 let kinds: Vec<Kind> = output.iter().map(Kind::of).collect();
808 for (index, item) in output
809 .into_iter()
810 .enumerate()
811 .filter(|(_, item)| item.is_object())
812 {
813 let known = self.restated(index, &item, &kinds, &taken).and_then(|at| {
814 let slot = self.slots.get(at)?;
815 Some((at, slot.open, slot.kind, slot.at))
816 });
817 let slot = match known {
818 Some((slot, false, kind, at)) => {
819 taken.insert(slot);
820 let ciphertext = item
821 .get("encrypted_content")
822 .filter(|cipher| cipher.as_str().is_some_and(|cipher| !cipher.is_empty()));
823 if let (Kind::Reasoning, Some(ciphertext)) = (kind, ciphertext) {
824 out.edit(at, |native| {
825 let stated = native
826 .str("encrypted_content")
827 .is_some_and(|cipher| !cipher.is_empty());
828 if let (false, Some(fields)) = (stated, native.as_object_mut()) {
829 fields.insert("encrypted_content".to_owned(), ciphertext.clone());
830 }
831 })?;
832 }
833 continue;
834 }
835 Some((slot, ..)) => slot,
836 None => self.added(
837 Some(index).filter(|index| !self.indexed.contains_key(index)),
838 &item,
839 &mut out,
840 )?,
841 };
842 taken.insert(slot);
843 self.done(slot, item, &mut out)?;
844 }
845 let open = self.slots.iter().any(|slot| slot.open);
848 self.release(!open, &mut out)?;
849 for slot in self.slots.iter_mut().filter(|slot| slot.open) {
851 if slot.kind == Kind::Call {
852 flush(slot, &mut out)?;
853 }
854 }
855 let (reason, error) = finish_reason_of(&response);
856 let end = Finish {
857 usage: response.get("usage").map(usage_of).unwrap_or_default(),
858 reason: Some(reason),
859 response_id: item_id(&response).map(str::to_owned),
860 model: response
861 .str("model")
862 .filter(|model| !model.is_empty())
863 .map(str::to_owned),
864 error,
865 };
866 Ok(out.end(end))
867 }
868}
869
870fn flush(slot: &Slot, out: &mut Out<'_, Completion>) -> Result<(), ProviderError> {
872 let arguments = streamed_arguments(slot);
873 let rest = remainder(slot, &arguments, out)?;
874 let fragment = CallFragment {
875 arguments: Some(&rest),
876 ..CallFragment::default()
877 };
878 out.fragment(Some(slot.at), fragment)
879}
880
881fn send(slot: &mut Slot, delta: &str, out: &mut Out<'_, Completion>) -> Result<(), ProviderError> {
887 if delta.is_empty() {
888 return Ok(());
889 }
890 let fragment = if slot.custom {
891 let quoted = serde_json::to_string(delta)?;
892 let open = quoted.strip_suffix('"').unwrap_or("ed);
893 Cow::Owned(match open.strip_prefix('"') {
894 Some(escaped) if !slot.arguments.is_empty() => escaped.to_owned(),
895 _ => format!("{{\"input\":{open}"),
896 })
897 } else {
898 Cow::Borrowed(delta)
899 };
900 slot.arguments.push_str(delta);
901 let fragment = CallFragment {
902 arguments: Some(&fragment),
903 ..CallFragment::default()
904 };
905 out.fragment(Some(slot.at), fragment)
906}
907
908fn remainder(
913 slot: &Slot,
914 arguments: &str,
915 out: &mut Out<'_, Completion>,
916) -> Result<String, ProviderError> {
917 let streamed = streamed_arguments(slot);
919 let sent = if slot.arguments.is_empty() {
920 ""
921 } else if slot.custom {
922 streamed.strip_suffix("\"}").unwrap_or(&streamed)
923 } else {
924 &streamed
925 };
926 Ok(match arguments.strip_prefix(sent) {
927 Some(rest) => rest.to_owned(),
928 None => {
929 out.restate(slot.at, arguments)?;
930 String::new()
931 }
932 })
933}
934
935fn streamed_arguments(slot: &Slot) -> String {
937 if slot.custom {
938 json!({ "input": slot.arguments }).to_string()
939 } else {
940 slot.arguments.clone()
941 }
942}
943
944fn output_index(frame: &Value) -> Result<Option<usize>, ProviderError> {
946 frame
947 .u64("output_index")
948 .map(|index| {
949 usize::try_from(index).map_err(|_| {
950 ProviderError::Response(format!("output_index {index} is out of range"))
951 })
952 })
953 .transpose()
954}
955
956impl<'id> Decoder<'id, Completion> for ResponsesDecoder {
957 type Event = ResponsesEvent;
958
959 fn classify(&self, frame: WireFrame) -> WireEvent<ResponsesEvent> {
960 classify_responses_payload(&frame.as_str())
961 }
962
963 fn decode(
964 &mut self,
965 event: ResponsesEvent,
966 mut out: Out<'id, Completion>,
967 ) -> Result<Flow, ProviderError> {
968 out.order_by_index();
971 match event {
972 ResponsesEvent::Frame {
973 kind,
974 mut frame,
975 raw,
976 } => match kind.as_str() {
977 "response.completed" | "response.incomplete" => {
981 let mut response = frame
982 .as_object_mut()
983 .and_then(|frame| frame.shift_remove("response"))
984 .filter(Value::is_object)
985 .unwrap_or_else(|| json!({}));
986 if let (true, Some(fields)) =
987 (kind == "response.incomplete", response.as_object_mut())
988 {
989 fields.insert("status".to_owned(), json!("incomplete"));
990 }
991 self.finish(response, out)
992 }
993 "response.failed" => {
994 Err(ProviderError::from_provider_body(raw.unwrap_or_default()))
995 }
996 "keepalive" => Ok(Flow::More),
997 kind if is_lifecycle_event(kind) => Ok(Flow::More),
998 kind => {
999 self.item_event(kind, frame, &mut out)?;
1000 Ok(Flow::More)
1001 }
1002 },
1003 ResponsesEvent::Whole(body) => self.finish(body, out),
1005 ResponsesEvent::Failure(raw) => Err(ProviderError::from_provider_body(raw)),
1006 ResponsesEvent::Sentinel => Ok(Flow::More),
1008 }
1009 }
1010}
1011
1012pub(crate) mod document;
1013
1014#[cfg(test)]
1015mod tests;