1use std::collections::{BTreeMap, HashMap, HashSet};
21
22use crate::completion::{CompletionRequest, CompletionResponse, FinishReason, Usage};
23use crate::error::ProviderError;
24use crate::message::citation;
25use crate::message::{
26 Api, AssistantContent, CallId, Image, LocalCallId, Opaque, Origin, Reasoning, Text, ToolCall,
27 ToolFunction, ToolName,
28};
29use crate::streaming::{Item, Part, PartKind, StreamEvent};
30use crate::telemetry::{GenAiOperation, SpanBuilder, SpanCombinator};
31use crate::wire::{
32 Assembled, Call, Descriptor, Emit, Fold, Mode, Operation, Out, Reply, Shared, WireCitation,
33};
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct Completion;
38
39impl Operation for Completion {
40 type Request = CompletionRequest;
41 type Event = StreamEvent;
42 type End = Finish;
43 type Response = CompletionResponse;
44 type Fold = ReplyFold;
45 type Emit = Assembled;
46
47 fn fold(request: &Self::Request, call: &mut Call<'_>) -> Self::Fold {
50 let telemetry = call
51 .wire
52 .telemetry
53 .map_or(GenAiOperation::Chat, |telemetry| telemetry(call.mode));
54 debug_assert!(telemetry.is_completion());
55 let replay = call
57 .wire
58 .replay
59 .map(|target| target.route(request).unwrap_or(target));
60 let model = request
61 .model
62 .as_deref()
63 .or(call.wire.model)
64 .or_else(|| replay.map(|target| target.model()))
65 .unwrap_or_default();
66 let span = SpanBuilder::new(call.wire.name, model, telemetry)
67 .streaming(matches!(call.mode, Mode::Streaming))
68 .system_instructions(
69 request.system_instructions(),
70 request.record_telemetry_content,
71 )
72 .build();
73 call.instrument(span.clone());
74 let api = replay.map_or_else(
75 || Api::from(call.wire.name.to_owned()),
76 |target| target.api(),
77 );
78 let mut origin = Origin::new(api, call.wire.name, model);
79 if replay.is_some_and(|target| target.binds_context(model)) {
81 origin.context =
82 replay.map(|target| crate::completion::history::context_of(request, target, model));
83 }
84 ReplyFold {
85 span,
86 facts: call.wire.facts.cloned().unwrap_or_default(),
87 wire: replay.is_some_and(|target| target.states_finish_reason()),
88 call_id_slot: replay.and_then(|target| target.call_id_slot()),
89 accept_unknown_finish: request.accept_unknown_finish_reasons,
90 ..ReplyFold::new(origin)
91 }
92 }
93
94 fn observed_usage(finish: &Finish) -> Option<&crate::completion::Usage> {
96 Some(&finish.usage)
97 }
98
99 fn prepare(
108 mut request: Self::Request,
109 wire: &Descriptor<'_>,
110 ) -> Result<Self::Request, ProviderError> {
111 let Some(target) = wire.replay else {
112 return Err(ProviderError::request(format!(
113 "completion wire `{}` names no replay target",
114 wire.name
115 )));
116 };
117 request.validate_message_content()?;
120 request.model = request
123 .model
124 .take()
125 .filter(|model| !model.is_empty())
126 .or_else(|| Some(target.model().to_owned()).filter(|model| !model.is_empty()));
127 let target = target.route(&request).unwrap_or(target);
128 crate::completion::options::check(target, &mut request)?;
131 let written = request.cache_points.iter().any(|point| {
134 matches!(
135 point,
136 crate::completion::CachePoint::Messages(_)
137 | crate::completion::CachePoint::Within { .. }
138 )
139 });
140 let written = written.then(|| {
141 let documents = (!target.takes_documents())
142 .then(|| request.normalized_documents())
143 .flatten();
144 (request.chat_history.clone(), documents)
145 });
146 let stored = target.continues_stored(&request);
147 let model = request.model.as_deref().unwrap_or(target.model());
148 let binds = target.binds_context(model) && !target.drops_unbound_items(&request);
150 let shape = crate::completion::history::Request {
151 model: request.model.as_deref(),
152 stored,
153 foreign_reasoning: request.foreign_reasoning,
154 tools: stored || target.declares_tools(&request),
157 context: binds.then(|| crate::completion::history::context_of(&request, target, model)),
158 };
159 let mut history = std::mem::take(&mut request.chat_history);
163 if !target.takes_documents() {
164 if let Some(documents) = request.normalized_documents() {
165 crate::completion::request::insert_after_leading_system(&mut history, documents);
166 }
167 request.documents.clear();
168 }
169 let mut kept = wire.prepared.and_then(|prepared| prepared.0.lock().ok());
171 let mut fresh = Default::default();
172 let memo = kept.as_deref_mut().unwrap_or(&mut fresh);
173 let adapted = crate::completion::history::adapt_for(history, target, &shape, memo);
174 drop(kept);
175 if let Some((written, documents)) = written {
176 request.cache_points = crate::completion::history::cache_points(
177 &request.cache_points,
178 &written,
179 documents.as_ref(),
180 &adapted,
181 target,
182 &shape,
183 );
184 }
185 request.chat_history = adapted;
186 request.validate_message_content()?;
187 Ok(request)
188 }
189}
190
191impl crate::wire::reply::Closing<Completion> for Assembled {
192 fn close(shared: &mut Shared<Completion>) {
193 let Shared { fold, items, .. } = shared;
194 fold.close_open(items);
195 }
196}
197
198impl Emit<Completion> for Assembled {}
199
200#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
202pub struct Finish {
203 pub usage: Usage,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub reason: Option<FinishReason>,
212 pub response_id: Option<String>,
214 pub model: Option<String>,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub error: Option<String>,
220}
221
222#[derive(Debug, Clone, PartialEq)]
224pub enum Block {
225 Text,
227 Reasoning {
229 redacted: bool,
231 },
232 Image(Image),
235 Call {
237 id: CallId,
239 name: ToolName,
241 },
242 Opaque {
244 replay: bool,
246 },
247}
248
249pub struct ReplyFold {
256 span: tracing::Span,
257 origin: Origin,
259 facts: crate::catalog::ModelFacts,
261 open: BTreeMap<usize, Draft>,
263 ended: HashMap<usize, usize>,
266 next_part: u32,
267 call_ids: HashSet<CallId>,
268 wire: bool,
271 call_id_slot: Option<&'static str>,
275 accept_unknown_finish: bool,
280 first_incomplete: Option<usize>,
284 unfinished: Vec<CallId>,
287 last_call: Option<usize>,
289 run: Option<usize>,
291 next_auto: usize,
292 by_index: Option<BTreeMap<usize, usize>>,
295 lead: Option<usize>,
297 choice: Vec<Option<AssistantContent>>,
299 open_text: BTreeMap<usize, AssistantContent>,
302}
303
304struct Draft {
306 part: Part,
307 started: bool,
309 item: serde_json::Value,
311 body: Body,
312 citations: Vec<WireCitation>,
314}
315
316#[derive(Clone, Copy, PartialEq, Eq)]
318enum Closing {
319 Complete,
321 Incomplete,
324}
325
326enum Body {
327 Text(String),
328 Image(Image),
329 Reasoning {
330 text: String,
331 redacted: bool,
332 },
333 Call {
334 id: Option<CallId>,
335 name: String,
336 arguments: Arguments,
337 },
338 Opaque {
339 replay: bool,
340 },
341}
342
343impl Body {
344 fn kind(&self) -> PartKind {
345 match self {
346 Self::Text(_) => PartKind::Text,
347 Self::Reasoning { .. } => PartKind::Reasoning,
348 Self::Image(_) => PartKind::Image,
349 Self::Call { .. } => PartKind::ToolCall,
350 Self::Opaque { .. } => PartKind::Opaque,
351 }
352 }
353}
354
355#[derive(Default)]
357struct Arguments {
358 text: String,
359 streamed: usize,
361 overflowed: bool,
362 substantive: bool,
364 announced: Option<serde_json::Value>,
367}
368
369const MAX_TOOL_INPUT_BYTES: usize = 32 * 1024 * 1024;
371
372impl Arguments {
373 fn push(&mut self, fragment: &str, name: &str) {
374 self.substantive |= !fragment.trim().is_empty();
375 if self.text.trim() == "null" && !fragment.trim().is_empty() {
378 self.text.clear();
379 }
380 if self.text.len().saturating_add(fragment.len()) > MAX_TOOL_INPUT_BYTES {
381 if !self.overflowed {
382 self.overflowed = true;
383 tracing::warn!(
384 tool = name,
385 "streamed tool-call input exceeded the accumulation bound; truncating"
386 );
387 }
388 } else {
389 self.text.push_str(fragment);
390 }
391 }
392
393 fn function(&self, name: ToolName) -> ToolFunction {
396 if self.text.is_empty()
397 && let Some(announced) = &self.announced
398 {
399 return ToolFunction::new(name, announced.clone());
400 }
401 let mut function = ToolFunction::parse(name, &self.text);
402 if self.overflowed {
403 function.invalid_arguments = Some(self.text.clone());
404 }
405 function
406 }
407
408 fn unstreamed(&mut self) -> Option<String> {
412 if "null".starts_with(self.text.trim()) {
413 return None;
414 }
415 let fragment = self.text.get(self.streamed..)?;
416 if fragment.is_empty() {
417 return None;
418 }
419 let fragment = fragment.to_owned();
420 self.streamed = self.text.len();
421 Some(fragment)
422 }
423
424 fn complete(&self) -> bool {
426 !self.overflowed && crate::json_utils::is_whole_object(&self.text)
427 }
428}
429
430fn stated(id: &str) -> bool {
433 !id.is_empty() && id != "null"
434}
435
436#[derive(Debug, Clone, Copy, Default)]
438pub struct CallFragment<'a> {
439 pub id: Option<&'a str>,
441 pub name: Option<&'a str>,
443 pub arguments: Option<&'a str>,
445}
446
447pub const AUTO_INDEX: usize = 1 << 30;
450
451fn not_open(index: usize) -> ProviderError {
452 ProviderError::Response(format!(
453 "the reply wrote to item {index}, which is not open"
454 ))
455}
456
457fn stream_call(items: &mut Items, draft: &mut Draft) {
461 let Body::Call {
462 name, arguments, ..
463 } = &mut draft.body
464 else {
465 return;
466 };
467 if !draft.started {
468 let Ok(name) = ToolName::new(name.clone()) else {
469 return;
470 };
471 draft.started = true;
472 emit(
473 items,
474 StreamEvent::Start {
475 part: draft.part,
476 kind: PartKind::ToolCall,
477 name: Some(name),
478 },
479 );
480 }
481 if let Some(json) = arguments.unstreamed() {
482 emit(
483 items,
484 StreamEvent::Arguments {
485 part: draft.part,
486 json,
487 },
488 );
489 }
490}
491
492impl ReplyFold {
493 pub(crate) fn new(origin: Origin) -> Self {
495 Self {
496 span: tracing::Span::none(),
497 origin,
498 facts: crate::catalog::ModelFacts::default(),
499 open: BTreeMap::new(),
500 ended: HashMap::new(),
501 next_part: 0,
502 call_ids: HashSet::new(),
503 wire: false,
504 call_id_slot: None,
505 accept_unknown_finish: false,
506 first_incomplete: None,
507 unfinished: Vec::new(),
508 last_call: None,
509 run: None,
510 next_auto: AUTO_INDEX,
511 by_index: None,
512 lead: None,
513 choice: Vec::new(),
514 open_text: BTreeMap::new(),
515 }
516 }
517
518 pub(crate) fn relayed(label: impl Into<String>) -> Self {
522 let label = label.into();
523 Self::new(Origin::new(label.clone(), label.clone(), label))
524 }
525
526 pub(crate) fn set_origin(&mut self, origin: Origin) {
528 self.origin = origin;
529 }
530
531 fn next(&mut self) -> Part {
533 let part = Part::new(self.next_part);
534 self.next_part += 1;
535 part
536 }
537
538 fn fresh_index(&mut self) -> usize {
539 let index = self.next_auto;
540 self.next_auto += 1;
541 index
542 }
543
544 fn draft(&mut self, index: usize) -> Result<&mut Draft, ProviderError> {
545 self.open.get_mut(&index).ok_or_else(|| not_open(index))
546 }
547
548 pub(crate) fn open_item(
549 &mut self,
550 items: &mut Items,
551 index: usize,
552 block: Block,
553 item: serde_json::Value,
554 ) -> Result<(), ProviderError> {
555 if self.open.contains_key(&index) {
556 return Err(ProviderError::Response(format!(
557 "the reply opened item {index} twice"
558 )));
559 }
560 let body = match block {
561 Block::Text => Body::Text(String::new()),
562 Block::Image(image) => Body::Image(image),
563 Block::Reasoning { redacted } => Body::Reasoning {
564 text: String::new(),
565 redacted,
566 },
567 Block::Call { id, name } => Body::Call {
568 id: Some(id),
569 name: name.into(),
570 arguments: Arguments::default(),
571 },
572 Block::Opaque { replay } => Body::Opaque { replay },
573 };
574 self.insert(index, body, item);
575 stream_call(items, self.draft(index)?);
576 Ok(())
577 }
578
579 fn insert(&mut self, index: usize, body: Body, item: serde_json::Value) {
580 self.ended.remove(&index);
581 let part = self.next();
582 if let Some(by_index) = &mut self.by_index {
583 by_index.insert(part.index(), index);
584 }
585 let started = false;
586 self.open.insert(
587 index,
588 Draft {
589 part,
590 started,
591 item,
592 body,
593 citations: Vec::new(),
594 },
595 );
596 }
597
598 pub(crate) fn push_item(
599 &mut self,
600 items: &mut Items,
601 index: usize,
602 fragment: &str,
603 ) -> Result<(), ProviderError> {
604 if fragment.is_empty() {
605 return Ok(());
606 }
607 let draft = self.draft(index)?;
608 let event = match &mut draft.body {
609 Body::Text(text) => {
610 text.push_str(fragment);
611 StreamEvent::Text {
612 part: draft.part,
613 text: fragment.to_owned(),
614 }
615 }
616 Body::Reasoning { text, .. } => {
617 text.push_str(fragment);
618 StreamEvent::Reasoning {
619 part: draft.part,
620 text: fragment.to_owned(),
621 }
622 }
623 Body::Call {
624 name, arguments, ..
625 } => {
626 arguments.push(fragment, name);
627 stream_call(items, draft);
628 return Ok(());
629 }
630 Body::Image(image) => {
631 if let crate::message::DocumentSourceKind::Base64(data) = &mut image.data {
632 data.push_str(fragment);
633 return Ok(());
634 }
635 return Err(ProviderError::Response(format!(
636 "the reply wrote data to the image item {index}, which holds no base64 data"
637 )));
638 }
639 Body::Opaque { .. } => {
640 return Err(ProviderError::Response(format!(
641 "the reply wrote text to the opaque item {index}"
642 )));
643 }
644 };
645 if !draft.started {
646 draft.started = true;
647 emit(
648 items,
649 StreamEvent::Start {
650 part: draft.part,
651 kind: draft.body.kind(),
652 name: None,
653 },
654 );
655 }
656 emit(items, event);
657 Ok(())
658 }
659
660 fn distinct_call_id(
667 &mut self,
668 id: Option<CallId>,
669 item: serde_json::Value,
670 ) -> (CallId, serde_json::Value) {
671 let (id, item) = match id {
672 Some(id) if !self.call_ids.contains(&id) => (id, item),
673 Some(id) => {
674 tracing::warn!(%id, "the provider named two tool calls with one id; renaming the second");
675 (CallId::Local(LocalCallId::new()), serde_json::Value::Null)
676 }
677 None if self.call_id_slot.is_some() => (CallId::Local(LocalCallId::new()), item),
678 None => (CallId::Local(LocalCallId::new()), serde_json::Value::Null),
679 };
680 self.call_ids.insert(id.clone());
681 (id, item)
682 }
683
684 fn close_item(
687 &mut self,
688 items: &mut Items,
689 index: usize,
690 closing: Closing,
691 ) -> Result<(), ProviderError> {
692 self.close_block(items, index, closing).map(drop)
693 }
694
695 fn close_block(
698 &mut self,
699 items: &mut Items,
700 index: usize,
701 closing: Closing,
702 ) -> Result<Option<CallId>, ProviderError> {
703 let mut draft = self.open.remove(&index).ok_or_else(|| not_open(index))?;
704 stream_call(items, &mut draft);
707 if self.run == Some(index) {
708 self.run = None;
709 }
710 self.ended.insert(index, draft.part.index());
711 let Draft {
712 part,
713 started,
714 item,
715 body,
716 citations,
717 } = draft;
718 let item = match (closing, &body) {
719 (Closing::Complete, _) | (Closing::Incomplete, Body::Opaque { .. }) => item,
720 (Closing::Incomplete, _) => {
721 self.cut_at(part.index());
722 serde_json::Value::Null
723 }
724 };
725 let content = match body {
726 Body::Text(text) => {
727 if text.is_empty() && item.is_null() {
728 return Ok(None);
729 }
730 let mut text = Text::new(text);
733 citation::attach(
734 &mut text,
735 Vec::new(),
736 citations,
737 &self.origin.provider,
738 index,
739 );
740 with_item(AssistantContent::Text(text), item)
741 }
742 Body::Reasoning { text, redacted } => {
743 if text.is_empty() && !redacted && item.is_null() {
744 return Ok(None);
745 }
746 let reasoning = Reasoning {
747 text,
748 redacted,
749 native: None,
750 };
751 with_item(AssistantContent::Reasoning(reasoning), item)
752 }
753 Body::Image(image) => with_item(AssistantContent::Image(image), item),
754 Body::Opaque { replay } => AssistantContent::Opaque(Opaque {
755 item,
756 replay: replay && closing == Closing::Complete,
757 }),
758 Body::Call {
759 id,
760 name,
761 arguments,
762 } => {
763 let Ok(name) = ToolName::new(name) else {
764 tracing::warn!(
765 index,
766 "the provider closed a tool call without a name; nothing can answer it"
767 );
768 return Ok(None);
769 };
770 let function = arguments.function(name);
771 let (id, item) = self.distinct_call_id(id, item);
772 if arguments.streamed == 0 {
775 let json = serde_json::Value::Object(function.arguments.clone()).to_string();
776 emit(items, StreamEvent::Arguments { part, json });
777 }
778 let call = ToolCall::new(id.clone(), function);
779 let content = with_item(AssistantContent::ToolCall(call), item);
780 emit(items, StreamEvent::End { part, content });
781 return Ok(Some(id));
782 }
783 };
784 if !started {
785 emit(
786 items,
787 StreamEvent::Start {
788 part,
789 kind: kind_of(&content),
790 name: None,
791 },
792 );
793 }
794 emit(items, StreamEvent::End { part, content });
795 Ok(None)
796 }
797
798 fn edit_ended(
801 &mut self,
802 items: &mut Items,
803 index: usize,
804 edit: impl FnOnce(&mut serde_json::Value),
805 ) -> Result<(), ProviderError> {
806 let position = *self.ended.get(&index).ok_or_else(|| not_open(index))?;
807 let queued = items.iter_mut().find_map(|item| match item {
808 Ok(Item::Event(StreamEvent::End { part, content })) if part.index() == position => {
809 Some(content)
810 }
811 _ => None,
812 });
813 let content = match queued {
814 Some(content) => Some(content),
815 None => self.choice.get_mut(position).and_then(Option::as_mut),
816 };
817 if let Some(item) = content.and_then(item_of) {
818 edit(item);
819 }
820 Ok(())
821 }
822
823 fn cite_item(
829 &mut self,
830 items: &mut Items,
831 index: usize,
832 citations: Vec<WireCitation>,
833 replace: bool,
834 ) {
835 let provider = self.origin.provider.as_str();
836 if let Some(draft) = self.open.get_mut(&index) {
837 if !matches!(draft.body, Body::Text(_)) {
838 tracing::warn!(
839 provider,
840 index,
841 "dropped citations of an item that is not text"
842 );
843 } else if replace {
844 draft.citations = citations;
845 } else {
846 draft.citations.extend(citations);
847 }
848 return;
849 }
850 let Some(position) = self.ended.get(&index).copied() else {
851 tracing::warn!(
852 provider,
853 index,
854 "dropped citations of an item the reply never opened"
855 );
856 return;
857 };
858 let queued = items.iter_mut().find_map(|item| match item {
859 Ok(Item::Event(StreamEvent::End { part, content })) if part.index() == position => {
860 Some(content)
861 }
862 _ => None,
863 });
864 let content = match queued {
865 Some(content) => Some(content),
866 None => self.choice.get_mut(position).and_then(Option::as_mut),
867 };
868 match content {
869 Some(AssistantContent::Text(text)) => {
870 let kept = if replace {
871 Vec::new()
872 } else {
873 text.citations().to_vec()
874 };
875 citation::attach(text, kept, citations, provider, index);
876 }
877 _ => tracing::warn!(
878 provider,
879 index,
880 "dropped citations of an item that is not text"
881 ),
882 }
883 }
884
885 pub(crate) fn run_item(
886 &mut self,
887 items: &mut Items,
888 block: Block,
889 fragment: &str,
890 ) -> Result<usize, ProviderError> {
891 let current = self.run.filter(|index| {
892 self.open.get(index).is_some_and(|draft| {
893 matches!(
894 (&draft.body, &block),
895 (Body::Text(_), Block::Text)
896 | (
897 Body::Reasoning {
898 redacted: false,
899 ..
900 },
901 Block::Reasoning { redacted: false }
902 )
903 )
904 })
905 });
906 let index = match current {
907 Some(index) => index,
908 None => {
909 self.end_run(items)?;
910 let index = self.fresh_index();
911 self.open_item(items, index, block, serde_json::Value::Null)?;
912 self.run = Some(index);
913 index
914 }
915 };
916 self.push_item(items, index, fragment)?;
917 Ok(index)
918 }
919
920 pub(crate) fn end_run(&mut self, items: &mut Items) -> Result<(), ProviderError> {
921 match self.run.take() {
922 Some(index) => self.close_item(items, index, Closing::Complete),
923 None => Ok(()),
924 }
925 }
926
927 pub(crate) fn write_content(
929 &mut self,
930 items: &mut Items,
931 content: AssistantContent,
932 ) -> Result<(), ProviderError> {
933 let content = match content {
934 AssistantContent::ToolCall(mut call) => {
935 let item = call
936 .native
937 .take()
938 .map_or(serde_json::Value::Null, |native| native.item);
939 let (id, item) = self.distinct_call_id(Some(call.id), item);
940 call.id = id;
941 with_item(AssistantContent::ToolCall(call), item)
942 }
943 content => content,
944 };
945 let part = self.next();
946 let name = match &content {
947 AssistantContent::ToolCall(call) => Some(call.function.name.clone()),
948 _ => None,
949 };
950 emit(
951 items,
952 StreamEvent::Start {
953 part,
954 kind: kind_of(&content),
955 name,
956 },
957 );
958 match &content {
959 AssistantContent::Text(text) if !text.text.is_empty() => emit(
960 items,
961 StreamEvent::Text {
962 part,
963 text: text.text.clone(),
964 },
965 ),
966 AssistantContent::Reasoning(reasoning) if !reasoning.text.is_empty() => emit(
967 items,
968 StreamEvent::Reasoning {
969 part,
970 text: reasoning.text.clone(),
971 },
972 ),
973 AssistantContent::ToolCall(call) => emit(
974 items,
975 StreamEvent::Arguments {
976 part,
977 json: call.function.arguments_json(),
978 },
979 ),
980 _ => {}
981 }
982 emit(items, StreamEvent::End { part, content });
983 Ok(())
984 }
985
986 pub(crate) fn close_open(&mut self, items: &mut Items) {
991 let mut open: Vec<(Part, usize)> = self
992 .open
993 .iter()
994 .map(|(index, draft)| (draft.part, *index))
995 .collect();
996 open.sort();
997 for (_, index) in open {
998 let closing = if self.run == Some(index) {
999 Closing::Complete
1000 } else {
1001 Closing::Incomplete
1002 };
1003 match self.close_block(items, index, closing) {
1004 Ok(Some(call)) if closing == Closing::Incomplete => self.unfinished.push(call),
1005 Ok(_) => {}
1006 Err(error) => items.push_back(Err(error)),
1007 }
1008 }
1009 }
1010
1011 fn cut_at(&mut self, position: usize) {
1013 self.first_incomplete = Some(
1014 self.first_incomplete
1015 .map_or(position, |first| first.min(position)),
1016 );
1017 }
1018
1019 pub fn origin(&self) -> &Origin {
1021 &self.origin
1022 }
1023
1024 pub(crate) fn partial(
1032 &self,
1033 end: Option<&Finish>,
1034 reply: &Reply,
1035 failure: Option<&ProviderError>,
1036 ) -> CompletionResponse {
1037 let mut response =
1038 self.response(end.cloned().unwrap_or_default(), reply.clone(), Vec::new());
1039 if end.is_none() {
1040 match failure {
1041 Some(error) => {
1042 response.error.get_or_insert_with(|| error.to_string());
1043 }
1044 None => {
1045 response.aborted = Some(
1046 "the caller stopped reading before the provider ended the reply".to_owned(),
1047 );
1048 }
1049 }
1050 }
1051 let unfinished =
1055 end.is_none() || !self.open.is_empty() || self.choice.iter().any(Option::is_none);
1056 let canonical_from = if unfinished {
1057 Some(0)
1058 } else {
1059 self.first_incomplete
1060 };
1061 let parts = self
1062 .choice
1063 .iter()
1064 .enumerate()
1065 .map(|(index, part)| {
1066 let part = part.clone().or_else(|| self.open_text.get(&index).cloned());
1067 (index, part)
1068 })
1069 .collect();
1070 response.choice = self.ordered(parts, canonical_from);
1071 response
1072 }
1073
1074 fn ordered(
1078 &self,
1079 parts: Vec<(usize, Option<AssistantContent>)>,
1080 canonical_from: Option<usize>,
1081 ) -> Vec<AssistantContent> {
1082 let mut parts: Vec<(usize, AssistantContent)> = parts
1083 .into_iter()
1084 .filter_map(|(position, part)| {
1085 let part = part?;
1086 Some(match canonical_from {
1087 Some(first) if position >= first => (position, canonical(part)),
1088 _ => (position, part),
1089 })
1090 })
1091 .collect();
1092 if let Some(by_index) = &self.by_index {
1093 parts.sort_by_key(|(position, _)| {
1094 (
1095 by_index.get(position).copied().unwrap_or(usize::MAX),
1096 *position,
1097 )
1098 });
1099 }
1100 if let Some(lead) = self.lead {
1101 parts.sort_by_key(|(position, _)| *position != lead);
1102 }
1103 parts.into_iter().map(|(_, part)| part).collect()
1104 }
1105
1106 fn response(
1108 &self,
1109 end: Finish,
1110 reply: Reply,
1111 choice: Vec<AssistantContent>,
1112 ) -> CompletionResponse {
1113 let Finish {
1114 usage,
1115 reason,
1116 response_id,
1117 model,
1118 error,
1119 } = end;
1120 use crate::provider_response::reported;
1121 let mut origin = self.origin.clone();
1122 origin.response_model = reported(model);
1123 if origin.model.is_empty()
1126 && let Some(model) = &origin.response_model
1127 {
1128 origin.model.clone_from(model);
1129 }
1130 origin.response_id = reported(response_id);
1131 let usage = priced(usage, &origin, &self.facts);
1132 let error = error.or_else(|| {
1133 if !self.wire {
1134 return None;
1135 }
1136 match &reason {
1137 None => Some("the provider ended the reply without a finish reason".to_owned()),
1138 Some(FinishReason::Length) => None,
1139 Some(_) if !self.unfinished.is_empty() => Some(
1140 "the provider ended the reply with a tool call it never finished".to_owned(),
1141 ),
1142 Some(_) => None,
1143 }
1144 });
1145 let mut response = CompletionResponse::new(choice, usage, origin, reply.raw)
1146 .with_optional_finish_reason(reason)
1147 .accept_unknown_finish_reasons(self.accept_unknown_finish)
1148 .with_unfinished_calls(self.unfinished.clone());
1149 response.error = error;
1150 response.provider_request_id = reported(reply.provider_request_id);
1151 response
1152 }
1153}
1154
1155fn priced(usage: Usage, origin: &Origin, facts: &crate::catalog::ModelFacts) -> Usage {
1158 if usage.cost.is_some() {
1159 return usage;
1160 }
1161 let cost = catalog_cost(origin, facts, &usage);
1162 usage.cost(cost)
1163}
1164
1165const UNPRICED: [&str; 3] = [
1171 crate::providers::chatgpt::PROVIDER_NAME,
1172 crate::providers::copilot::PROVIDER_NAME,
1173 crate::providers::ollama::PROVIDER_NAME,
1174];
1175
1176fn catalog_cost(
1181 origin: &Origin,
1182 facts: &crate::catalog::ModelFacts,
1183 usage: &Usage,
1184) -> Option<crate::completion::Cost> {
1185 if UNPRICED.contains(&origin.provider.as_str()) {
1186 return None;
1187 }
1188 facts
1189 .for_model(&origin.provider, &origin.model)
1190 .and_then(|spec| spec.pricing.as_ref())
1191 .and_then(|pricing| pricing.cost(usage))
1192}
1193
1194pub(crate) fn canonical(block: AssistantContent) -> AssistantContent {
1196 match block {
1197 AssistantContent::Opaque(opaque) => AssistantContent::Opaque(Opaque {
1198 replay: false,
1199 ..opaque
1200 }),
1201 block => block.into_canonical(),
1202 }
1203}
1204
1205fn item_of(content: &mut AssistantContent) -> Option<&mut serde_json::Value> {
1207 let native = match content {
1208 AssistantContent::Text(text) => text.native.as_mut(),
1209 AssistantContent::ToolCall(call) => call.native.as_mut(),
1210 AssistantContent::Reasoning(reasoning) => reasoning.native.as_mut(),
1211 AssistantContent::Image(image) => image.native.as_mut(),
1212 AssistantContent::Opaque(opaque) => return Some(&mut opaque.item),
1213 };
1214 native.map(|native| &mut native.item)
1215}
1216
1217fn with_item(block: AssistantContent, item: serde_json::Value) -> AssistantContent {
1219 if item.is_null() {
1220 block
1221 } else {
1222 block.with_native(item)
1223 }
1224}
1225
1226fn kind_of(content: &AssistantContent) -> PartKind {
1227 match content {
1228 AssistantContent::Text(_) => PartKind::Text,
1229 AssistantContent::Reasoning(_) => PartKind::Reasoning,
1230 AssistantContent::ToolCall(_) => PartKind::ToolCall,
1231 AssistantContent::Image(_) => PartKind::Image,
1232 AssistantContent::Opaque(_) => PartKind::Opaque,
1233 }
1234}
1235
1236pub(crate) type Items = std::collections::VecDeque<Result<Item<StreamEvent>, ProviderError>>;
1237
1238fn emit(items: &mut Items, event: StreamEvent) {
1239 items.push_back(Ok(Item::Event(event)));
1240}
1241
1242pub fn merge(item: &mut serde_json::Value, delta: &serde_json::Map<String, serde_json::Value>) {
1247 use serde_json::Value;
1248 if !item.is_object() {
1249 *item = Value::Object(serde_json::Map::new());
1250 }
1251 let Value::Object(item) = item else {
1252 return;
1253 };
1254 for (key, value) in delta {
1255 if key == "type" {
1256 continue;
1257 }
1258 match (item.get_mut(key), value) {
1259 (Some(Value::String(existing)), Value::String(fragment)) => {
1260 existing.push_str(fragment);
1261 }
1262 (Some(Value::Array(existing)), Value::Array(more)) => {
1263 existing.extend(more.iter().cloned());
1264 }
1265 _ => {
1266 item.insert(key.clone(), value.clone());
1267 }
1268 }
1269 }
1270}
1271
1272impl Fold<Completion> for ReplyFold {
1273 fn absorb(&mut self, event: &StreamEvent) -> Result<(), ProviderError> {
1274 match event {
1275 StreamEvent::Start { part, .. } => {
1276 if self.choice.len() <= part.index() {
1277 self.choice.resize(part.index() + 1, None);
1278 }
1279 }
1280 StreamEvent::End { part, content } => {
1281 if self.choice.len() <= part.index() {
1282 self.choice.resize(part.index() + 1, None);
1283 }
1284 if let Some(slot) = self.choice.get_mut(part.index()) {
1285 *slot = Some(content.clone());
1286 }
1287 self.open_text.remove(&part.index());
1288 }
1289 StreamEvent::Text { part, text } => {
1290 if let AssistantContent::Text(open) = self
1291 .open_text
1292 .entry(part.index())
1293 .or_insert_with(|| AssistantContent::text(""))
1294 {
1295 open.text.push_str(text);
1296 }
1297 }
1298 StreamEvent::Reasoning { part, text } => {
1299 if let AssistantContent::Reasoning(open) = self
1300 .open_text
1301 .entry(part.index())
1302 .or_insert_with(|| AssistantContent::reasoning(""))
1303 {
1304 open.text.push_str(text);
1305 }
1306 }
1307 StreamEvent::Arguments { .. } => {}
1308 }
1309 Ok(())
1310 }
1311
1312 fn finish(mut self, end: Finish, reply: Reply) -> Result<CompletionResponse, ProviderError> {
1313 if let Some(FinishReason::Other(reason)) = &end.reason {
1314 tracing::warn!(
1315 provider = %self.origin.provider,
1316 reason = %reason,
1317 accepted = self.accept_unknown_finish,
1318 "the provider ended the reply with an unknown finish reason"
1319 );
1320 }
1321 let parts = std::mem::take(&mut self.choice).into_iter().enumerate();
1325 let choice = self.ordered(parts.collect(), self.first_incomplete);
1326 let response = self.response(end, reply, choice);
1327 self.span
1328 .record_response(response.response_id(), response.model(), &response.usage);
1329 Ok(response)
1330 }
1331}
1332
1333impl<'id> Out<'id, Completion> {
1334 pub(crate) fn catalog_cost(&self, usage: &Usage) -> Option<crate::completion::Cost> {
1338 let shared = self.lock();
1339 catalog_cost(&shared.fold.origin, &shared.fold.facts, usage)
1340 }
1341
1342 pub fn open(
1347 &mut self,
1348 index: usize,
1349 block: Block,
1350 item: serde_json::Value,
1351 ) -> Result<(), ProviderError> {
1352 let mut shared = self.lock();
1353 let Shared { fold, items, .. } = &mut *shared;
1354 fold.open_item(items, index, block, item)
1355 }
1356
1357 pub fn push(&mut self, index: usize, fragment: &str) -> Result<(), ProviderError> {
1360 let mut shared = self.lock();
1361 let Shared { fold, items, .. } = &mut *shared;
1362 fold.push_item(items, index, fragment)
1363 }
1364
1365 pub fn edit(
1371 &mut self,
1372 index: usize,
1373 edit: impl FnOnce(&mut serde_json::Value),
1374 ) -> Result<(), ProviderError> {
1375 let mut shared = self.lock();
1376 let Shared { fold, items, .. } = &mut *shared;
1377 match fold.open.get_mut(&index) {
1378 Some(draft) => {
1379 edit(&mut draft.item);
1380 Ok(())
1381 }
1382 None => fold.edit_ended(items, index, edit),
1383 }
1384 }
1385
1386 pub fn close(&mut self, index: usize) -> Result<(), ProviderError> {
1394 let mut shared = self.lock();
1395 let Shared { fold, items, .. } = &mut *shared;
1396 fold.close_item(items, index, Closing::Incomplete)
1397 }
1398
1399 pub fn finish(&mut self, index: usize) -> Result<(), ProviderError> {
1402 let mut shared = self.lock();
1403 let Shared { fold, items, .. } = &mut *shared;
1404 fold.close_item(items, index, Closing::Complete)
1405 }
1406
1407 pub fn finish_open(&mut self) -> Result<(), ProviderError> {
1410 let mut shared = self.lock();
1411 let Shared { fold, items, .. } = &mut *shared;
1412 let mut open: Vec<(Part, usize)> = fold
1413 .open
1414 .iter()
1415 .map(|(index, draft)| (draft.part, *index))
1416 .collect();
1417 open.sort();
1418 for (_, index) in open {
1419 fold.close_item(items, index, Closing::Complete)?;
1420 }
1421 Ok(())
1422 }
1423
1424 pub fn whole(
1428 &mut self,
1429 index: usize,
1430 block: Block,
1431 item: serde_json::Value,
1432 text: &str,
1433 ) -> Result<(), ProviderError> {
1434 self.open(index, block, item)?;
1435 self.push(index, text)?;
1436 self.finish(index)
1437 }
1438
1439 pub fn order_by_index(&mut self) {
1444 let mut shared = self.lock();
1445 if shared.fold.by_index.is_none() {
1446 shared.fold.by_index = Some(BTreeMap::new());
1447 }
1448 }
1449
1450 pub fn lead(&mut self, index: usize) -> Result<(), ProviderError> {
1455 let mut shared = self.lock();
1456 let position = shared.fold.draft(index)?.part.index();
1457 shared.fold.lead.get_or_insert(position);
1458 Ok(())
1459 }
1460
1461 pub fn restate(&mut self, index: usize, text: &str) -> Result<(), ProviderError> {
1466 let mut shared = self.lock();
1467 let draft = shared.fold.draft(index)?;
1468 match &mut draft.body {
1469 Body::Text(body) | Body::Reasoning { text: body, .. } => text.clone_into(body),
1470 Body::Call { arguments, .. } => {
1471 text.clone_into(&mut arguments.text);
1472 arguments.streamed = if draft.started { text.len() } else { 0 };
1474 }
1475 Body::Image(_) | Body::Opaque { .. } => {}
1476 }
1477 Ok(())
1478 }
1479
1480 pub fn fragment(
1493 &mut self,
1494 index: Option<usize>,
1495 fragment: CallFragment<'_>,
1496 ) -> Result<(), ProviderError> {
1497 let mut shared = self.lock();
1498 let Shared {
1499 fold: turn, items, ..
1500 } = &mut *shared;
1501 let new_id = fragment.id.filter(|id| stated(id));
1502 let index = match index {
1503 Some(index) => {
1504 let names = fragment.name.is_some_and(|name| !name.is_empty());
1505 let held = turn.open.get(&index).and_then(|draft| match &draft.body {
1506 Body::Call {
1507 id: Some(id),
1508 arguments,
1509 ..
1510 } if new_id.is_some_and(|new| id.wire() != new) => Some(arguments.complete()),
1511 _ => None,
1512 });
1513 if let Some(complete) = held.filter(|complete| *complete || names) {
1514 let moved = turn.fresh_index();
1516 if let Some(draft) = turn.open.remove(&index) {
1517 turn.open.insert(moved, draft);
1518 }
1519 let closing = if complete {
1520 Closing::Complete
1521 } else {
1522 Closing::Incomplete
1523 };
1524 turn.close_item(items, moved, closing)?;
1525 }
1526 index
1527 }
1528 None => {
1529 let owner = new_id.and_then(|new| {
1530 turn.open
1531 .iter()
1532 .find_map(|(index, draft)| match &draft.body {
1533 Body::Call { id: Some(id), .. } if id.wire() == new => Some(*index),
1534 _ => None,
1535 })
1536 });
1537 let continues = |last: &usize| {
1538 turn.open.get(last).is_some_and(|draft| {
1539 matches!(&draft.body, Body::Call { arguments, .. } if !arguments.complete())
1540 })
1541 };
1542 match (owner, new_id, turn.last_call) {
1543 (Some(index), _, _) => index,
1544 (None, None, Some(last)) if continues(&last) => last,
1545 _ => turn.fresh_index(),
1546 }
1547 }
1548 };
1549 turn.last_call = Some(index);
1550 if !turn.open.contains_key(&index) {
1551 let body = Body::Call {
1552 id: None,
1553 name: String::new(),
1554 arguments: Arguments::default(),
1555 };
1556 turn.insert(index, body, serde_json::Value::Null);
1557 }
1558 let Body::Call {
1559 id,
1560 name,
1561 arguments,
1562 } = &mut turn.draft(index)?.body
1563 else {
1564 return Err(ProviderError::Response(format!(
1565 "the reply sent a call fragment for item {index}, which is not a call"
1566 )));
1567 };
1568 if let Some(call_id) = fragment.id.filter(|id| stated(id)) {
1569 *id = Some(CallId::from_wire(call_id));
1570 }
1571 if name.is_empty()
1574 && let Some(fragment) = fragment.name.filter(|name| !name.is_empty())
1575 {
1576 fragment.clone_into(name);
1577 }
1578 if let Some(fragment) = fragment.arguments {
1579 arguments.push(fragment, name);
1580 }
1581 stream_call(items, turn.draft(index)?);
1582 Ok(())
1583 }
1584
1585 pub fn announce(
1588 &mut self,
1589 index: usize,
1590 announced: serde_json::Value,
1591 ) -> Result<(), ProviderError> {
1592 if let Body::Call { arguments, .. } = &mut self.lock().fold.draft(index)?.body {
1593 arguments.announced = Some(announced);
1594 }
1595 Ok(())
1596 }
1597
1598 pub fn fresh_index(&mut self) -> usize {
1600 self.lock().fold.fresh_index()
1601 }
1602
1603 pub fn run(&mut self, block: Block, fragment: &str) -> Result<usize, ProviderError> {
1608 let mut shared = self.lock();
1609 let Shared { fold, items, .. } = &mut *shared;
1610 fold.run_item(items, block, fragment)
1611 }
1612
1613 pub fn end_run(&mut self) -> Result<(), ProviderError> {
1616 let mut shared = self.lock();
1617 let Shared { fold, items, .. } = &mut *shared;
1618 fold.end_run(items)
1619 }
1620
1621 pub fn cite(&mut self, index: usize, citation: WireCitation) {
1628 let mut shared = self.lock();
1629 let Shared { fold, items, .. } = &mut *shared;
1630 fold.cite_item(items, index, vec![citation], false);
1631 }
1632
1633 pub fn set_citations(&mut self, index: usize, citations: Vec<WireCitation>) {
1636 let mut shared = self.lock();
1637 let Shared { fold, items, .. } = &mut *shared;
1638 fold.cite_item(items, index, citations, true);
1639 }
1640
1641 #[cfg(any(test, feature = "test-utils"))]
1644 pub(crate) fn content(&mut self, content: AssistantContent) -> Result<(), ProviderError> {
1645 let mut shared = self.lock();
1646 let Shared { fold, items, .. } = &mut *shared;
1647 fold.write_content(items, content)
1648 }
1649}
1650
1651pub(crate) fn events_of(
1654 response: &CompletionResponse,
1655) -> Result<Vec<Item<StreamEvent>>, ProviderError> {
1656 let mut turn = ReplyFold::new(response.origin.clone());
1657 let mut items = Items::new();
1658 for content in &response.choice {
1659 turn.write_content(&mut items, content.clone())?;
1660 }
1661 items.into_iter().collect()
1662}
1663
1664#[cfg(test)]
1665mod tests;