Skip to main content

rig_core/operation/
completion.rs

1//! Generating an assistant turn. A completion decoder writes its reply
2//! through move-only part handles, and the writer emits each part's events
3//! in order: a fragment needs its part's handle, closing a part consumes it,
4//! and ending the reply closes every part still open, in the order they
5//! opened. The provider's end of the reply is a [`Finish`], which the fold
6//! needs to produce the response.
7//!
8//! ```
9//! use rig_core::operation::Finish;
10//! use rig_core::completion::FinishReason;
11//!
12//! let finish = Finish {
13//!     reason: Some(FinishReason::Stop),
14//!     ..Finish::default()
15//! };
16//! assert_eq!(finish.reason, Some(FinishReason::Stop));
17//! ```
18
19use std::collections::{BTreeMap, HashSet};
20use std::marker::PhantomData;
21
22use crate::completion::{CompletionRequest, CompletionResponse, FinishReason, Usage};
23use crate::error::{MalformedToolInput, ProviderError};
24use crate::message::{
25    AdditionalParams, AssistantContent, CallId, Image, Issuer, LocalCallId, ProviderCallId,
26    Reasoning, ReasoningContent, Text, ToolCall, ToolFunction, ToolName,
27};
28use crate::streaming::{Item, Part, PartKind, StreamEvent};
29use crate::telemetry::{GenAiOperation, SpanBuilder, SpanCombinator};
30use crate::wire::{Assembled, Call, Emit, Fold, Mode, Operation, Out, Reply, Shared};
31
32/// Generating an assistant turn, unary or streamed.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct Completion;
35
36impl Operation for Completion {
37    type Request = CompletionRequest;
38    type Event = StreamEvent;
39    type End = Finish;
40    type Response = CompletionResponse;
41    type Fold = Turn;
42    type Emit = Assembled;
43
44    /// The call's span names the model the request overrides to, when it
45    /// names one: every wire honours the override on encode.
46    fn fold(request: &Self::Request, call: &mut Call<'_>) -> Self::Fold {
47        let telemetry = call.wire.telemetry.map_or_else(
48            || match call.mode {
49                Mode::Unary => GenAiOperation::Chat,
50                Mode::Streaming => GenAiOperation::ChatStreaming,
51            },
52            |telemetry| telemetry(call.mode),
53        );
54        debug_assert!(telemetry.is_completion());
55        let model = request
56            .model
57            .as_deref()
58            .or(call.wire.model)
59            .unwrap_or_default();
60        let span = SpanBuilder::new(call.wire.name, model, telemetry)
61            .system_instructions(
62                request.system_instructions(),
63                request.record_telemetry_content,
64            )
65            .build();
66        call.instrument(span.clone());
67        Turn {
68            span,
69            ..Turn::new(call.wire.name)
70        }
71    }
72
73    /// [`CompletionRequest::validate_message_content`].
74    fn validate(request: &Self::Request) -> Result<(), ProviderError> {
75        request.validate_message_content()
76    }
77}
78
79impl crate::wire::reply::Closing<Completion> for Assembled {
80    fn close(shared: &mut Shared<Completion>) {
81        let Shared { fold, items, .. } = shared;
82        fold.close_open(items);
83    }
84}
85
86impl Emit<Completion> for Assembled {}
87
88/// What the provider sends when it ends a completion reply.
89#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
90pub struct Finish {
91    /// Token usage the provider reported. A counter it did not report is
92    /// `None`.
93    pub usage: Usage,
94    /// Why the model stopped, when the provider said.
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub reason: Option<FinishReason>,
97    /// The assistant message id, for replay. A message id the decoder
98    /// recorded while the reply was open outranks it.
99    pub message_id: Option<String>,
100    /// The response id. Never replayed as a message id.
101    pub response_id: Option<String>,
102    /// The model the provider reports.
103    pub model: Option<String>,
104}
105
106/// The completion fold. The driver and the bus writer build it; no other
107/// code can feed one.
108///
109/// It is both sides of one reply: the writer state a decoder writes through
110/// (open parts, the pending-call buffer, the issuer of the reply's
111/// reasoning) and the parts the consumer has taken, in their position.
112pub struct Turn {
113    span: tracing::Span,
114    /// The provider the reply is from, and the default issuer of its
115    /// reasoning.
116    provider: String,
117    // The writer.
118    drafts: Vec<Draft>,
119    next_part: u32,
120    issuer: Option<Issuer>,
121    message_id: Option<String>,
122    pending: BTreeMap<usize, Pending>,
123    provider_ids: HashSet<ProviderCallId>,
124    // The fold.
125    choice: Vec<Option<AssistantContent>>,
126    /// The text the consumer took of parts still open, by position.
127    open_text: BTreeMap<usize, String>,
128}
129
130/// A part a handle names, as the writer holds it.
131enum Draft {
132    Text {
133        part: Option<Part>,
134        text: String,
135        params: Option<AdditionalParams>,
136    },
137    Reasoning {
138        part: Option<Part>,
139        text: String,
140    },
141    Call {
142        id: CallId,
143        name: ToolName,
144        arguments: Arguments,
145        signature: Option<String>,
146        additional_params: Option<serde_json::Value>,
147    },
148    Closed,
149}
150
151/// A tool call's argument text as it arrives.
152#[derive(Default)]
153struct Arguments {
154    text: String,
155    overflowed: bool,
156    /// Whether any fragment carried a non-blank byte.
157    substantive: bool,
158    /// Arguments the provider announced when the call opened, used only
159    /// when no fragment arrives.
160    announced: Option<serde_json::Value>,
161}
162
163/// The most argument bytes one call accumulates.
164const MAX_TOOL_INPUT_BYTES: usize = 32 * 1024 * 1024;
165
166impl Arguments {
167    fn push(&mut self, fragment: &str, name: &str) {
168        self.substantive |= !fragment.trim().is_empty();
169        // Some OpenAI-compatible gateways send a literal `null` before the
170        // real fragments; a non-blank fragment supersedes it.
171        if self.text.trim() == "null" && !fragment.trim().is_empty() {
172            self.text.clear();
173        }
174        if self.text.len().saturating_add(fragment.len()) > MAX_TOOL_INPUT_BYTES {
175            if !self.overflowed {
176                self.overflowed = true;
177                tracing::warn!(
178                    tool = name,
179                    "streamed tool-call input exceeded the accumulation bound; truncating"
180                );
181            }
182        } else {
183            self.text.push_str(fragment);
184        }
185    }
186
187    /// The arguments as JSON: the announced ones when no fragment arrived.
188    fn parse(&self) -> Result<serde_json::Value, serde_json::Error> {
189        if self.text.is_empty()
190            && let Some(announced) = &self.announced
191        {
192            return Ok(announced.clone());
193        }
194        let arguments = crate::json_utils::parse_tool_arguments(&self.text)?;
195        if self.overflowed {
196            return Err(serde::de::Error::custom(
197                "tool-call input exceeded the accumulation bound",
198            ));
199        }
200        Ok(arguments)
201    }
202}
203
204/// One tool call the provider streams under a wire index, until its id and
205/// name are both known and it opens.
206#[derive(Default)]
207struct Pending {
208    id: Option<String>,
209    item_id: Option<String>,
210    name: String,
211    arguments: Arguments,
212    signature: Option<String>,
213    additional_params: Option<serde_json::Value>,
214    /// The call's handle, once it opened.
215    open: Option<usize>,
216}
217
218/// What to do with a call whose arguments do not parse when it closes.
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum IfMalformed {
221    /// Fail the reply with [`ProviderError::MalformedToolInput`]: the
222    /// provider said the call was complete.
223    Fail,
224    /// Deliver the call with `{}` arguments: the provider superseded it
225    /// mid-assembly.
226    EmptyObject,
227    /// Drop it: its input never fully arrived.
228    Drop,
229    /// Leave it open: the close was a probe, and more input may follow.
230    KeepOpen,
231}
232
233/// What one fragment of a buffered tool call carries.
234#[derive(Debug, Clone, Copy, Default)]
235pub struct CallFragment<'a> {
236    /// The provider's id for the call.
237    pub id: Option<&'a str>,
238    /// The output-item id a dual-identifier wire issues beside it.
239    pub item_id: Option<&'a str>,
240    /// The tool's name.
241    pub name: Option<&'a str>,
242    /// A fragment of the argument JSON.
243    pub arguments: Option<&'a str>,
244}
245
246/// How a closed reasoning part ends: its provider id, a signature, or the
247/// provider's whole restatement of it.
248#[derive(Debug, Clone, Default, PartialEq)]
249pub struct Seal {
250    /// The provider's id for the reasoning item.
251    pub id: Option<String>,
252    /// A signature for the reasoning text.
253    pub signature: Option<String>,
254    /// The provider's authoritative restatement, which supersedes the
255    /// fragments.
256    pub restated: Option<Reasoning>,
257}
258
259type Brand<'id> = PhantomData<fn(&'id ()) -> &'id ()>;
260
261/// An open text part of one reply. Only opening the part gives one, so a
262/// fragment cannot precede its part's start:
263///
264/// ```compile_fail,E0599
265/// use rig_core::operation::{Completion, TextPart};
266/// use rig_core::wire::Out;
267///
268/// fn early(out: &mut Out<'_, Completion>) {
269///     out.push_text(&TextPart::default(), "before the start");
270/// }
271/// ```
272///
273/// Nor can one be built from its fields to write into a part:
274///
275/// ```compile_fail,E0451
276/// use rig_core::operation::{Completion, TextPart};
277/// use rig_core::wire::Out;
278///
279/// fn forge<'id>(out: &mut Out<'id, Completion>) {
280///     let part = TextPart { slot: 0, brand: std::marker::PhantomData };
281///     out.push_text(&part, "into a part this code never opened");
282/// }
283/// ```
284#[must_use = "an open part is closed when the reply ends"]
285#[derive(Debug)]
286pub struct TextPart<'id> {
287    slot: usize,
288    brand: Brand<'id>,
289}
290
291/// An open reasoning part of one reply.
292#[must_use = "an open part is closed when the reply ends"]
293#[derive(Debug)]
294pub struct ReasoningPart<'id> {
295    slot: usize,
296    brand: Brand<'id>,
297}
298
299/// An open tool call of one reply. It has its id and its name, and becomes
300/// visible when it closes.
301///
302/// ```compile_fail,E0382
303/// use rig_core::operation::{CallPart, Completion};
304/// use rig_core::wire::Out;
305///
306/// // A closed call cannot be written again.
307/// fn twice<'id>(out: &mut Out<'id, Completion>, part: CallPart<'id>) {
308///     let _ = out.close_call(part);
309///     out.push_arguments(&part, "{}");
310/// }
311/// ```
312#[must_use = "an open call that is not closed never becomes visible"]
313#[derive(Debug)]
314pub struct CallPart<'id> {
315    slot: usize,
316    brand: Brand<'id>,
317}
318
319impl Turn {
320    /// The writer of a reply from `provider`.
321    pub(crate) fn new(provider: impl Into<String>) -> Self {
322        Self {
323            span: tracing::Span::none(),
324            provider: provider.into(),
325            drafts: Vec::new(),
326            next_part: 0,
327            issuer: None,
328            message_id: None,
329            pending: BTreeMap::new(),
330            provider_ids: HashSet::new(),
331            choice: Vec::new(),
332            open_text: BTreeMap::new(),
333        }
334    }
335
336    /// The issuer this reply's reasoning is sealed to.
337    fn issuer(&self) -> Issuer {
338        self.issuer
339            .clone()
340            .unwrap_or_else(|| Issuer::from(self.provider.clone()))
341    }
342
343    fn draft(&mut self, draft: Draft) -> usize {
344        self.drafts.push(draft);
345        self.drafts.len() - 1
346    }
347
348    /// The next position in the choice.
349    fn next(&mut self) -> Part {
350        let part = Part::new(self.next_part);
351        self.next_part += 1;
352        part
353    }
354
355    fn start(&mut self, items: &mut Items, kind: PartKind) -> Part {
356        let part = self.next();
357        emit(items, StreamEvent::Start { part, kind });
358        part
359    }
360
361    fn push_text(&mut self, items: &mut Items, slot: usize, fragment: &str) {
362        if fragment.is_empty() {
363            return;
364        }
365        let started = match self.drafts.get(slot) {
366            Some(Draft::Text { part, .. }) => *part,
367            _ => return,
368        };
369        let part = match started {
370            Some(part) => part,
371            None => {
372                let part = self.start(items, PartKind::Text);
373                if let Some(Draft::Text {
374                    part: slot_part, ..
375                }) = self.drafts.get_mut(slot)
376                {
377                    *slot_part = Some(part);
378                }
379                part
380            }
381        };
382        if let Some(Draft::Text { text, .. }) = self.drafts.get_mut(slot) {
383            text.push_str(fragment);
384        }
385        emit(
386            items,
387            StreamEvent::Text {
388                part,
389                text: fragment.to_owned(),
390            },
391        );
392    }
393
394    fn close_text(&mut self, items: &mut Items, slot: usize) {
395        let Some(Draft::Text { part, text, params }) = self
396            .drafts
397            .get_mut(slot)
398            .map(|draft| std::mem::replace(draft, Draft::Closed))
399        else {
400            return;
401        };
402        // A text part survives with text or with the metadata it carries.
403        if text.is_empty() && params.is_none() {
404            return;
405        }
406        let part = part.unwrap_or_else(|| self.start(items, PartKind::Text));
407        emit(
408            items,
409            StreamEvent::End {
410                part,
411                content: AssistantContent::Text(Text {
412                    text,
413                    additional_params: params,
414                }),
415            },
416        );
417    }
418
419    fn push_reasoning(&mut self, items: &mut Items, slot: usize, fragment: &str) {
420        if fragment.is_empty() {
421            return;
422        }
423        let started = match self.drafts.get(slot) {
424            Some(Draft::Reasoning { part, .. }) => *part,
425            _ => return,
426        };
427        let part = match started {
428            Some(part) => part,
429            None => {
430                let part = self.start(items, PartKind::Reasoning);
431                if let Some(Draft::Reasoning {
432                    part: slot_part, ..
433                }) = self.drafts.get_mut(slot)
434                {
435                    *slot_part = Some(part);
436                }
437                part
438            }
439        };
440        if let Some(Draft::Reasoning { text, .. }) = self.drafts.get_mut(slot) {
441            text.push_str(fragment);
442        }
443        emit(
444            items,
445            StreamEvent::Reasoning {
446                part,
447                text: fragment.to_owned(),
448            },
449        );
450    }
451
452    fn close_reasoning(&mut self, items: &mut Items, slot: usize, seal: Seal) {
453        let Some(Draft::Reasoning { part, text }) = self
454            .drafts
455            .get_mut(slot)
456            .map(|draft| std::mem::replace(draft, Draft::Closed))
457        else {
458            return;
459        };
460        let Seal {
461            id,
462            signature,
463            restated,
464        } = seal;
465        let reasoning = match restated {
466            Some(mut restated) => {
467                // An omitted restatement id must not erase an established one.
468                if restated.id.is_none() {
469                    restated.id = id;
470                }
471                if let Some(signature) = signature {
472                    attach_signature(&mut restated, signature);
473                }
474                restated
475            }
476            None if !text.is_empty() => Reasoning {
477                id,
478                content: vec![ReasoningContent::Text { text, signature }],
479            },
480            // A signature with nothing streamed to sign is replay state of
481            // its own.
482            None => match signature {
483                Some(signature) => Reasoning {
484                    id,
485                    content: vec![ReasoningContent::Text {
486                        text: String::new(),
487                        signature: Some(signature),
488                    }],
489                },
490                None => return,
491            },
492        };
493        let part = part.unwrap_or_else(|| self.start(items, PartKind::Reasoning));
494        let content = AssistantContent::Reasoning(reasoning.sealed(self.issuer()));
495        emit(items, StreamEvent::End { part, content });
496    }
497
498    fn open_call(&mut self, id: CallId, name: ToolName) -> Result<usize, ProviderError> {
499        if let Some(provider) = id.provider()
500            && !self.provider_ids.insert(provider.clone())
501        {
502            return Err(ProviderError::DuplicateCallId(id));
503        }
504        Ok(self.draft(Draft::Call {
505            id,
506            name,
507            arguments: Arguments::default(),
508            signature: None,
509            additional_params: None,
510        }))
511    }
512
513    fn push_arguments(&mut self, slot: usize, fragment: &str) {
514        if let Some(Draft::Call {
515            name, arguments, ..
516        }) = self.drafts.get_mut(slot)
517        {
518            arguments.push(fragment, name.as_str());
519        }
520    }
521
522    /// Close the call in `slot`: it becomes visible with its arguments, or
523    /// `if_malformed` decides.
524    fn close_call(
525        &mut self,
526        items: &mut Items,
527        slot: usize,
528        if_malformed: IfMalformed,
529    ) -> Result<(), ProviderError> {
530        let parsed = match self.drafts.get(slot) {
531            Some(Draft::Call { arguments, .. }) => arguments.parse(),
532            _ => return Ok(()),
533        };
534        let parsed = match (parsed, if_malformed) {
535            (Ok(arguments), _) => arguments,
536            (Err(_), IfMalformed::KeepOpen) => return Ok(()),
537            (Err(_), IfMalformed::EmptyObject) => serde_json::Value::Object(Default::default()),
538            (Err(_), IfMalformed::Drop) => {
539                if let Some(draft) = self.drafts.get_mut(slot) {
540                    *draft = Draft::Closed;
541                }
542                return Ok(());
543            }
544            (Err(error), IfMalformed::Fail) => {
545                let Some(Draft::Call {
546                    id,
547                    name,
548                    arguments,
549                    ..
550                }) = self
551                    .drafts
552                    .get_mut(slot)
553                    .map(|draft| std::mem::replace(draft, Draft::Closed))
554                else {
555                    return Ok(());
556                };
557                return Err(ProviderError::MalformedToolInput(MalformedToolInput {
558                    name: name.into(),
559                    id,
560                    raw: arguments.text,
561                    error: error.to_string(),
562                }));
563            }
564        };
565        let Some(Draft::Call {
566            id,
567            name,
568            arguments,
569            signature,
570            additional_params,
571        }) = self
572            .drafts
573            .get_mut(slot)
574            .map(|draft| std::mem::replace(draft, Draft::Closed))
575        else {
576            return Ok(());
577        };
578        let json = if arguments.text.is_empty() {
579            parsed.to_string()
580        } else {
581            arguments.text
582        };
583        let part = self.start(items, PartKind::ToolCall);
584        emit(items, StreamEvent::Arguments { part, json });
585        emit(
586            items,
587            StreamEvent::End {
588                part,
589                content: AssistantContent::ToolCall(ToolCall {
590                    id,
591                    function: ToolFunction {
592                        name,
593                        arguments: parsed,
594                    },
595                    signature,
596                    additional_params,
597                }),
598            },
599        );
600        Ok(())
601    }
602
603    /// The buffered call at `index`, opened when its id and name are both
604    /// known. A wire that sends no id gets one rig issues when the call
605    /// closes (`issue`).
606    fn open_pending(&mut self, index: usize, issue: bool) -> Result<Option<usize>, ProviderError> {
607        let Some(pending) = self.pending.get(&index) else {
608            return Ok(None);
609        };
610        if let Some(slot) = pending.open {
611            return Ok(Some(slot));
612        }
613        let Ok(name) = ToolName::new(pending.name.clone()) else {
614            return Ok(None);
615        };
616        let id = match (&pending.id, &pending.item_id) {
617            (Some(call_id), item_id) => match ProviderCallId::new(call_id.clone()) {
618                Some(provider) => CallId::Provider(match item_id {
619                    Some(item_id) => provider.with_item_id(item_id.clone()),
620                    None => provider,
621                }),
622                None => CallId::Local(LocalCallId::new()),
623            },
624            (None, _) if issue => CallId::Local(LocalCallId::new()),
625            (None, _) => return Ok(None),
626        };
627        let slot = self.open_call(id, name)?;
628        let Some(pending) = self.pending.get_mut(&index) else {
629            return Ok(None);
630        };
631        pending.open = Some(slot);
632        let arguments = std::mem::take(&mut pending.arguments);
633        let signature = pending.signature.take();
634        let additional_params = pending.additional_params.take();
635        if let Some(Draft::Call {
636            arguments: open,
637            signature: open_signature,
638            additional_params: open_params,
639            ..
640        }) = self.drafts.get_mut(slot)
641        {
642            *open = arguments;
643            *open_signature = signature;
644            *open_params = additional_params;
645        }
646        Ok(Some(slot))
647    }
648
649    /// Close every part still open, in the order they opened. A call closes
650    /// when its arguments parse; one whose input never completed is
651    /// dropped, and so is every call still buffered.
652    pub(crate) fn close_open(&mut self, items: &mut Items) {
653        self.pending.clear();
654        for slot in 0..self.drafts.len() {
655            match self.drafts.get(slot) {
656                Some(Draft::Text { .. }) => self.close_text(items, slot),
657                Some(Draft::Reasoning { .. }) => self.close_reasoning(items, slot, Seal::default()),
658                Some(Draft::Call { .. }) => {
659                    let _ = self.close_call(items, slot, IfMalformed::Drop);
660                }
661                Some(Draft::Closed) | None => {}
662            }
663        }
664    }
665
666    /// The parts taken so far, in their position; a part that has not
667    /// ended is not among them.
668    pub fn snapshot(&self) -> Vec<AssistantContent> {
669        self.choice.iter().flatten().cloned().collect()
670    }
671
672    /// The assistant message id the decoder recorded, if any.
673    pub fn message_id(&self) -> Option<&str> {
674        self.message_id.as_deref()
675    }
676
677    /// The issuer this reply's reasoning is sealed to.
678    pub fn reasoning_issuer(&self) -> Issuer {
679        self.issuer()
680    }
681
682    /// The provider the reply is from.
683    pub fn provider(&self) -> &str {
684        &self.provider
685    }
686
687    /// What arrived so far as a response: every part that ended, the text
688    /// the consumer already took of a text part still open, and the
689    /// provider's end when it arrived.
690    pub(crate) fn partial(&self, end: Option<&Finish>, reply: &Reply) -> CompletionResponse {
691        let mut response = self.response(end.cloned().unwrap_or_default(), reply.clone());
692        response.choice = self
693            .choice
694            .iter()
695            .enumerate()
696            .filter_map(|(index, part)| {
697                part.clone().or_else(|| {
698                    self.open_text
699                        .get(&index)
700                        .map(|text| AssistantContent::text(text.clone()))
701                })
702            })
703            .map(|part| match part {
704                AssistantContent::Reasoning(reasoning) => {
705                    AssistantContent::Reasoning(reasoning.reseal(self.issuer()))
706                }
707                part => part,
708            })
709            .collect();
710        response
711    }
712
713    fn response(&self, end: Finish, reply: Reply) -> CompletionResponse {
714        let issuer = self.issuer();
715        let choice = self
716            .snapshot()
717            .into_iter()
718            .map(|part| match part {
719                AssistantContent::Reasoning(reasoning) => {
720                    AssistantContent::Reasoning(reasoning.reseal(issuer.clone()))
721                }
722                part => part,
723            })
724            .collect();
725        let Finish {
726            usage,
727            reason,
728            message_id,
729            response_id,
730            model,
731        } = end;
732        use crate::provider_response::reported;
733        let mut response = CompletionResponse::new(choice, usage, reply.provider, reply.raw)
734            .with_optional_finish_reason(reason);
735        // A message id the decoder recorded outranks the end's.
736        response.message_id = reported(self.message_id.clone().or(message_id));
737        response.response_id = reported(response_id);
738        response.model = reported(model);
739        response.provider_request_id = reported(reply.provider_request_id);
740        response
741    }
742}
743
744pub(crate) type Items = std::collections::VecDeque<Result<Item<StreamEvent>, ProviderError>>;
745
746fn emit(items: &mut Items, event: StreamEvent) {
747    items.push_back(Ok(Item::Event(event)));
748}
749
750/// Attach a signature to the last unsigned reasoning text, or add a
751/// signature-only text: replay needs every signature.
752fn attach_signature(reasoning: &mut Reasoning, signature: String) {
753    match reasoning
754        .content
755        .iter_mut()
756        .rev()
757        .find_map(|content| match content {
758            ReasoningContent::Text {
759                signature: slot @ None,
760                ..
761            } => Some(slot),
762            _ => None,
763        }) {
764        Some(slot) => *slot = Some(signature),
765        None => reasoning.content.push(ReasoningContent::Text {
766            text: String::new(),
767            signature: Some(signature),
768        }),
769    }
770}
771
772impl Fold<Completion> for Turn {
773    fn absorb(&mut self, event: &StreamEvent) -> Result<(), ProviderError> {
774        match event {
775            StreamEvent::Start { part, .. } => {
776                if self.choice.len() <= part.index() {
777                    self.choice.resize(part.index() + 1, None);
778                }
779            }
780            StreamEvent::End { part, content } => {
781                if self.choice.len() <= part.index() {
782                    self.choice.resize(part.index() + 1, None);
783                }
784                if let Some(slot) = self.choice.get_mut(part.index()) {
785                    *slot = Some(content.clone());
786                }
787                // A relayed reply's reasoning names its issuer on its seal.
788                if let (None, AssistantContent::Reasoning(reasoning)) = (&self.issuer, content) {
789                    self.issuer = Some(reasoning.issuer().clone());
790                }
791                self.open_text.remove(&part.index());
792            }
793            StreamEvent::Text { part, text } => {
794                self.open_text
795                    .entry(part.index())
796                    .or_default()
797                    .push_str(text);
798            }
799            StreamEvent::Reasoning { .. } | StreamEvent::Arguments { .. } => {}
800        }
801        Ok(())
802    }
803
804    fn finish(self, end: Finish, reply: Reply) -> Result<CompletionResponse, ProviderError> {
805        let response = self.response(end, reply);
806        self.span.record_response(
807            response
808                .response_id
809                .as_deref()
810                .or(response.message_id.as_deref()),
811            response.model.as_deref(),
812            &response.usage,
813        );
814        Ok(response)
815    }
816}
817
818impl<'id> Out<'id, Completion> {
819    /// Open a text part. Nothing is emitted until its first fragment.
820    pub fn text(&mut self) -> TextPart<'id> {
821        let slot = self.lock().fold.draft(Draft::Text {
822            part: None,
823            text: String::new(),
824            params: None,
825        });
826        TextPart {
827            slot,
828            brand: PhantomData,
829        }
830    }
831
832    /// Append to an open text part.
833    pub fn push_text(&mut self, part: &TextPart<'id>, text: &str) {
834        let mut shared = self.lock();
835        let Shared { fold, items, .. } = &mut *shared;
836        fold.push_text(items, part.slot, text);
837    }
838
839    /// Merge provider metadata into an open text part. Metadata is content:
840    /// the part starts here if no text started it.
841    pub fn text_params(&mut self, part: &TextPart<'id>, additional_params: AdditionalParams) {
842        let mut shared = self.lock();
843        let Shared { fold, items, .. } = &mut *shared;
844        let unstarted = matches!(
845            fold.drafts.get(part.slot),
846            Some(Draft::Text { part: None, .. })
847        );
848        if unstarted {
849            let started = fold.start(items, PartKind::Text);
850            if let Some(Draft::Text { part, .. }) = fold.drafts.get_mut(part.slot) {
851                *part = Some(started);
852            }
853        }
854        if let Some(Draft::Text { params, .. }) = fold.drafts.get_mut(part.slot) {
855            match params {
856                Some(params) => params.merge(additional_params),
857                None => *params = Some(additional_params),
858            }
859        }
860    }
861
862    /// Close a text part. One with neither text nor metadata is dropped.
863    pub fn close_text(&mut self, part: TextPart<'id>) {
864        let mut shared = self.lock();
865        let Shared { fold, items, .. } = &mut *shared;
866        fold.close_text(items, part.slot);
867    }
868
869    /// Open a reasoning part. Nothing is emitted until its first fragment.
870    pub fn reasoning(&mut self) -> ReasoningPart<'id> {
871        let slot = self.lock().fold.draft(Draft::Reasoning {
872            part: None,
873            text: String::new(),
874        });
875        ReasoningPart {
876            slot,
877            brand: PhantomData,
878        }
879    }
880
881    /// Append to an open reasoning part.
882    pub fn push_reasoning(&mut self, part: &ReasoningPart<'id>, text: &str) {
883        let mut shared = self.lock();
884        let Shared { fold, items, .. } = &mut *shared;
885        fold.push_reasoning(items, part.slot, text);
886    }
887
888    /// Close a reasoning part, sealed to the reply's issuer. One with
889    /// nothing to replay is dropped.
890    pub fn close_reasoning(&mut self, part: ReasoningPart<'id>, seal: Seal) {
891        let mut shared = self.lock();
892        let Shared { fold, items, .. } = &mut *shared;
893        fold.close_reasoning(items, part.slot, seal);
894    }
895
896    /// A whole reasoning part the provider sent in one piece.
897    pub fn reasoning_block(&mut self, reasoning: Reasoning) {
898        let part = self.reasoning();
899        self.close_reasoning(
900            part,
901            Seal {
902                restated: Some(reasoning),
903                ..Seal::default()
904            },
905        );
906    }
907
908    /// Open a tool call with its id and name. A provider id already used by
909    /// another call of this reply is [`ProviderError::DuplicateCallId`].
910    ///
911    /// Both are required, so no call opens without an id:
912    ///
913    /// ```compile_fail,E0308
914    /// use rig_core::message::ToolName;
915    /// use rig_core::operation::Completion;
916    /// use rig_core::wire::Out;
917    ///
918    /// fn idless(out: &mut Out<'_, Completion>, name: ToolName) {
919    ///     let _ = out.call(None, name);
920    /// }
921    /// ```
922    ///
923    /// or without a name:
924    ///
925    /// ```compile_fail,E0308
926    /// use rig_core::message::CallId;
927    /// use rig_core::operation::Completion;
928    /// use rig_core::wire::Out;
929    ///
930    /// fn nameless(out: &mut Out<'_, Completion>, id: CallId) {
931    ///     let _ = out.call(id, "");
932    /// }
933    /// ```
934    pub fn call(&mut self, id: CallId, name: ToolName) -> Result<CallPart<'id>, ProviderError> {
935        let slot = self.lock().fold.open_call(id, name)?;
936        Ok(CallPart {
937            slot,
938            brand: PhantomData,
939        })
940    }
941
942    /// Append a fragment of an open call's argument JSON.
943    pub fn push_arguments(&mut self, part: &CallPart<'id>, json: &str) {
944        self.lock().fold.push_arguments(part.slot, json);
945    }
946
947    /// Attach a provider signature and metadata to an open call.
948    pub fn decorate_call(
949        &mut self,
950        part: &CallPart<'id>,
951        signature: Option<String>,
952        additional_params: Option<serde_json::Value>,
953    ) {
954        if let Some(Draft::Call {
955            signature: open_signature,
956            additional_params: open_params,
957            ..
958        }) = self.lock().fold.drafts.get_mut(part.slot)
959        {
960            if signature.is_some() {
961                *open_signature = signature;
962            }
963            if additional_params.is_some() {
964                *open_params = additional_params;
965            }
966        }
967    }
968
969    /// Close a call: it becomes visible. Arguments that do not parse are
970    /// [`ProviderError::MalformedToolInput`].
971    pub fn close_call(&mut self, part: CallPart<'id>) -> Result<(), ProviderError> {
972        let mut shared = self.lock();
973        let Shared { fold, items, .. } = &mut *shared;
974        fold.close_call(items, part.slot, IfMalformed::Fail)
975    }
976
977    /// Drop an open call: it never becomes visible.
978    pub fn abandon_call(&mut self, part: CallPart<'id>) {
979        if let Some(draft) = self.lock().fold.drafts.get_mut(part.slot) {
980            *draft = Draft::Closed;
981        }
982    }
983
984    /// A whole tool call the provider sent in one piece.
985    pub fn tool_call(&mut self, call: ToolCall) -> Result<(), ProviderError> {
986        let ToolCall {
987            id,
988            function,
989            signature,
990            additional_params,
991        } = call;
992        let part = self.call(id, function.name)?;
993        self.decorate_call(&part, signature, additional_params);
994        let mut shared = self.lock();
995        let Shared { fold, items, .. } = &mut *shared;
996        if let Some(Draft::Call { arguments, .. }) = fold.drafts.get_mut(part.slot) {
997            arguments.announced = Some(function.arguments);
998        }
999        fold.close_call(items, part.slot, IfMalformed::Fail)
1000    }
1001
1002    /// An image part.
1003    pub fn image(&mut self, image: Image) {
1004        let mut shared = self.lock();
1005        let Shared { fold, items, .. } = &mut *shared;
1006        let part = fold.start(items, PartKind::Image);
1007        emit(
1008            items,
1009            StreamEvent::End {
1010                part,
1011                content: AssistantContent::Image(image),
1012            },
1013        );
1014    }
1015
1016    /// A whole part of an already assembled response.
1017    pub fn content(&mut self, content: AssistantContent) -> Result<(), ProviderError> {
1018        match content {
1019            AssistantContent::Text(text) => {
1020                let part = self.text();
1021                self.push_text(&part, &text.text);
1022                if let Some(params) = text.additional_params {
1023                    self.text_params(&part, params);
1024                }
1025                self.close_text(part);
1026            }
1027            AssistantContent::Reasoning(reasoning) => {
1028                let issuer = reasoning.issuer().clone();
1029                self.issued_by(issuer.clone());
1030                if let Some(reasoning) = reasoning.open(&issuer) {
1031                    self.reasoning_block(reasoning.clone());
1032                }
1033            }
1034            AssistantContent::ToolCall(call) => self.tool_call(call)?,
1035            AssistantContent::Image(image) => self.image(image),
1036        }
1037        Ok(())
1038    }
1039
1040    /// Record the assistant message id. It outranks the one the end names.
1041    pub fn message_id(&mut self, id: impl Into<String>) {
1042        let id = id.into();
1043        if !id.is_empty() {
1044            self.lock().fold.message_id = Some(id);
1045        }
1046    }
1047
1048    /// Name the issuer of this reply's reasoning: a gateway relaying
1049    /// another provider's models.
1050    pub fn issued_by(&mut self, issuer: impl Into<Issuer>) {
1051        self.lock().fold.issuer = Some(issuer.into());
1052    }
1053
1054    /// Buffer one fragment of the tool call the provider streams under
1055    /// `index`. The call opens when its id and name are both known; a
1056    /// provider id another call already has is
1057    /// [`ProviderError::DuplicateCallId`].
1058    pub fn call_fragment(
1059        &mut self,
1060        index: usize,
1061        fragment: CallFragment<'_>,
1062    ) -> Result<(), ProviderError> {
1063        let mut shared = self.lock();
1064        let turn = &mut shared.fold;
1065        let pending = turn.pending.entry(index).or_default();
1066        if let Some(id) = fragment.id.filter(|id| !id.is_empty()) {
1067            pending.id = Some(id.to_owned());
1068        }
1069        if let Some(item_id) = fragment.item_id.filter(|id| !id.is_empty()) {
1070            pending.item_id = Some(item_id.to_owned());
1071        }
1072        if let Some(name) = fragment.name.filter(|name| !name.is_empty()) {
1073            name.clone_into(&mut pending.name);
1074        }
1075        let open = pending.open;
1076        if let Some(arguments) = fragment.arguments.filter(|arguments| !arguments.is_empty()) {
1077            match open {
1078                Some(slot) => turn.push_arguments(slot, arguments),
1079                None => {
1080                    let name = pending.name.clone();
1081                    pending.arguments.push(arguments, &name);
1082                }
1083            }
1084        }
1085        turn.open_pending(index, false)?;
1086        Ok(())
1087    }
1088
1089    /// Arguments the provider announced for the buffered call at `index`,
1090    /// used only if no fragment arrives.
1091    pub fn announce_pending(&mut self, index: usize, arguments: serde_json::Value) {
1092        let mut shared = self.lock();
1093        let turn = &mut shared.fold;
1094        let Some(pending) = turn.pending.get_mut(&index) else {
1095            return;
1096        };
1097        match pending.open {
1098            Some(slot) => {
1099                if let Some(Draft::Call {
1100                    arguments: open, ..
1101                }) = turn.drafts.get_mut(slot)
1102                {
1103                    open.announced = Some(arguments);
1104                }
1105            }
1106            None => pending.arguments.announced = Some(arguments),
1107        }
1108    }
1109
1110    /// Attach a signature and metadata to the buffered call the provider
1111    /// names `provider_id`. What it already has wins.
1112    pub fn decorate_pending(
1113        &mut self,
1114        provider_id: &str,
1115        signature: Option<String>,
1116        additional_params: Option<serde_json::Value>,
1117    ) {
1118        if provider_id.is_empty() {
1119            return;
1120        }
1121        let mut shared = self.lock();
1122        let turn = &mut shared.fold;
1123        let Some(pending) = turn
1124            .pending
1125            .values_mut()
1126            .find(|pending| pending.id.as_deref() == Some(provider_id))
1127        else {
1128            return;
1129        };
1130        match pending.open {
1131            Some(slot) => {
1132                if let Some(Draft::Call {
1133                    signature: open_signature,
1134                    additional_params: open_params,
1135                    ..
1136                }) = turn.drafts.get_mut(slot)
1137                {
1138                    if open_signature.is_none() {
1139                        *open_signature = signature;
1140                    }
1141                    if open_params.is_none() {
1142                        *open_params = additional_params;
1143                    }
1144                }
1145            }
1146            None => {
1147                if pending.signature.is_none() {
1148                    pending.signature = signature;
1149                }
1150                if pending.additional_params.is_none() {
1151                    pending.additional_params = additional_params;
1152                }
1153            }
1154        }
1155    }
1156
1157    /// Attach a signature and metadata to the buffered call at `index`.
1158    pub fn decorate_pending_at(
1159        &mut self,
1160        index: usize,
1161        signature: Option<String>,
1162        additional_params: Option<serde_json::Value>,
1163    ) {
1164        let mut shared = self.lock();
1165        let turn = &mut shared.fold;
1166        let Some(pending) = turn.pending.get_mut(&index) else {
1167            return;
1168        };
1169        match pending.open {
1170            Some(slot) => {
1171                if let Some(Draft::Call {
1172                    signature: open_signature,
1173                    additional_params: open_params,
1174                    ..
1175                }) = turn.drafts.get_mut(slot)
1176                {
1177                    if signature.is_some() {
1178                        *open_signature = signature;
1179                    }
1180                    if additional_params.is_some() {
1181                        *open_params = additional_params;
1182                    }
1183                }
1184            }
1185            None => {
1186                if signature.is_some() {
1187                    pending.signature = signature;
1188                }
1189                if additional_params.is_some() {
1190                    pending.additional_params = additional_params;
1191                }
1192            }
1193        }
1194    }
1195
1196    /// The wire indices of the buffered calls, in order.
1197    pub fn pending_calls(&self) -> Vec<usize> {
1198        self.lock().fold.pending.keys().copied().collect()
1199    }
1200
1201    /// The provider id of the buffered call at `index`, when it has one.
1202    pub fn pending_id(&self, index: usize) -> Option<String> {
1203        self.lock()
1204            .fold
1205            .pending
1206            .get(&index)
1207            .and_then(|pending| pending.id.clone())
1208    }
1209
1210    /// The tool name the buffered call at `index` has so far.
1211    pub fn pending_name(&self, index: usize) -> String {
1212        self.lock()
1213            .fold
1214            .pending
1215            .get(&index)
1216            .map(|pending| pending.name.clone())
1217            .unwrap_or_default()
1218    }
1219
1220    /// Whether the buffered call at `index` received argument bytes that are
1221    /// not blank, or announced arguments.
1222    pub fn pending_has_arguments(&self, index: usize) -> bool {
1223        let shared = self.lock();
1224        let turn = &shared.fold;
1225        let Some(pending) = turn.pending.get(&index) else {
1226            return false;
1227        };
1228        let arguments = match pending.open {
1229            Some(slot) => match turn.drafts.get(slot) {
1230                Some(Draft::Call { arguments, .. }) => arguments,
1231                _ => return false,
1232            },
1233            None => &pending.arguments,
1234        };
1235        arguments.substantive || arguments.announced.is_some()
1236    }
1237
1238    /// Close the buffered call at `index`: it becomes visible, under the
1239    /// provider's id or, for a wire that sent none, one rig issues. A call
1240    /// with no name is dropped; `if_malformed` decides for one whose
1241    /// arguments do not parse.
1242    pub fn close_pending(
1243        &mut self,
1244        index: usize,
1245        if_malformed: IfMalformed,
1246    ) -> Result<(), ProviderError> {
1247        let mut shared = self.lock();
1248        let Shared { fold, items, .. } = &mut *shared;
1249        let Some(slot) = fold.open_pending(index, true)? else {
1250            fold.pending.remove(&index);
1251            return Ok(());
1252        };
1253        let result = fold.close_call(items, slot, if_malformed);
1254        let kept_open = matches!(fold.drafts.get(slot), Some(Draft::Call { .. }));
1255        if !kept_open {
1256            fold.pending.remove(&index);
1257        }
1258        result
1259    }
1260
1261    /// Drop the buffered call at `index`: it never becomes visible.
1262    pub fn drop_pending(&mut self, index: usize) {
1263        let mut shared = self.lock();
1264        let turn = &mut shared.fold;
1265        if let Some(pending) = turn.pending.remove(&index)
1266            && let Some(slot) = pending.open
1267            && let Some(draft) = turn.drafts.get_mut(slot)
1268        {
1269            *draft = Draft::Closed;
1270        }
1271    }
1272}
1273
1274impl Turn {
1275    /// The fold of a stream relayed from another fold, which built its
1276    /// events: it only collects them.
1277    pub(crate) fn relayed(provider: impl Into<String>) -> Self {
1278        Self::new(provider)
1279    }
1280
1281    // The bus writer writes through these, holding the parts it opened by
1282    // their slot.
1283
1284    pub(crate) fn open_text(&mut self) -> usize {
1285        self.draft(Draft::Text {
1286            part: None,
1287            text: String::new(),
1288            params: None,
1289        })
1290    }
1291
1292    pub(crate) fn write_text(&mut self, items: &mut Items, slot: usize, text: &str) {
1293        self.push_text(items, slot, text);
1294    }
1295
1296    pub(crate) fn end_text(&mut self, items: &mut Items, slot: usize) {
1297        self.close_text(items, slot);
1298    }
1299
1300    pub(crate) fn open_reasoning(&mut self) -> usize {
1301        self.draft(Draft::Reasoning {
1302            part: None,
1303            text: String::new(),
1304        })
1305    }
1306
1307    pub(crate) fn write_reasoning(&mut self, items: &mut Items, slot: usize, text: &str) {
1308        self.push_reasoning(items, slot, text);
1309    }
1310
1311    pub(crate) fn end_reasoning(&mut self, items: &mut Items, slot: usize) {
1312        self.close_reasoning(items, slot, Seal::default());
1313    }
1314
1315    pub(crate) fn write_call(
1316        &mut self,
1317        items: &mut Items,
1318        call: ToolCall,
1319    ) -> Result<(), ProviderError> {
1320        let ToolCall {
1321            id,
1322            function,
1323            signature,
1324            additional_params,
1325        } = call;
1326        let slot = self.open_call(id, function.name)?;
1327        if let Some(Draft::Call {
1328            arguments,
1329            signature: open_signature,
1330            additional_params: open_params,
1331            ..
1332        }) = self.drafts.get_mut(slot)
1333        {
1334            arguments.announced = Some(function.arguments);
1335            *open_signature = signature;
1336            *open_params = additional_params;
1337        }
1338        self.close_call(items, slot, IfMalformed::Fail)
1339    }
1340}
1341
1342/// The events a stream of `response` would have carried: each part whole,
1343/// in order.
1344pub(crate) fn events_of(
1345    response: &CompletionResponse,
1346) -> Result<Vec<Item<StreamEvent>>, ProviderError> {
1347    let shared = std::sync::Mutex::new(Shared::new(Turn::new(response.provider.clone())));
1348    {
1349        let mut out = Out::new(&shared);
1350        for content in &response.choice {
1351            out.content(content.clone())?;
1352        }
1353    }
1354    shared
1355        .into_inner()
1356        .unwrap_or_else(std::sync::PoisonError::into_inner)
1357        .items
1358        .into_iter()
1359        .collect()
1360}
1361
1362#[cfg(test)]
1363mod tests;