Skip to main content

rig_core/operation/
completion.rs

1//! Generating an assistant turn. A completion decoder writes its reply one
2//! provider item at a time, keyed by the item's wire index: the item opens
3//! when its first event arrives and takes the next position in the reply,
4//! its fragments grow it, and closing it finalizes its block with the
5//! provider's item as the block's native. Nothing regroups: blocks keep the
6//! order their items opened in. The provider's end of the reply is a
7//! [`Finish`], which the fold needs to produce the response.
8//!
9//! ```
10//! use rig_core::operation::Finish;
11//! use rig_core::completion::FinishReason;
12//!
13//! let finish = Finish {
14//!     reason: Some(FinishReason::Stop),
15//!     ..Finish::default()
16//! };
17//! assert_eq!(finish.reason, Some(FinishReason::Stop));
18//! ```
19
20use 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/// Generating an assistant turn, unary or streamed.
36#[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    /// The call's span names the model the request overrides to, when it
48    /// names one: every wire honours the override on encode.
49    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        // A wire that picks its API per request records the one it used.
56        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        // Only a wire that binds items to the context needs it recorded.
80        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    /// The reply's normalized usage.
95    fn observed_usage(finish: &Finish) -> Option<&crate::completion::Usage> {
96        Some(&finish.usage)
97    }
98
99    /// Resolve the model the request addresses once, as the request's
100    /// `model`: the one it names, else the wire's. The history is checked
101    /// with [`CompletionRequest::validate_message_content`], then
102    /// [`adapt`](crate::completion::adapt)ed for that model on the wire's
103    /// replay target and checked again. Encoders, the fold and replay all
104    /// read the one resolved model. Each option the routed target refuses
105    /// is reported first, through
106    /// [`options::check`](crate::completion::options::check).
107    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        // The caller's history is checked as written, so a rejection names
118        // its own messages; adapting never empties a message it keeps.
119        request.validate_message_content()?;
120        // A wire that addresses no model (an interaction read back) leaves
121        // it to the reply, which names the model the turn is from.
122        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        // Every option the routed wire cannot honour is reported here, before
129        // any wire encodes, whatever its encoder does.
130        crate::completion::options::check(target, &mut request)?;
131        // Message cache points name the history as written; they are moved
132        // with it once it is adapted.
133        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        // Only a target that binds items to the context reads it.
149        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            // A conversation the provider stores holds its own tools, so a
155            // continuation that declares none still calls them.
156            tools: stored || target.declares_tools(&request),
157            context: binds.then(|| crate::completion::history::context_of(&request, target, model)),
158        };
159        // The history moves into the adapter. Documents join it first, so
160        // the adapter's rules apply to them, unless the encoder sends them
161        // itself.
162        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        // What the model's last request left; another request waits for it.
170        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/// What the provider sends when it ends a completion reply.
201#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
202pub struct Finish {
203    /// Token usage the provider reported. A counter it did not report is
204    /// `None`. A cost the provider reported goes in `usage.cost` and is
205    /// kept; without one the fold prices the counters at the catalog's
206    /// [`Pricing`](crate::catalog::Pricing) for the request's model, and
207    /// leaves the cost `None` when the catalog has no price for it.
208    pub usage: Usage,
209    /// Why the model stopped, when the provider said.
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub reason: Option<FinishReason>,
212    /// The response id.
213    pub response_id: Option<String>,
214    /// The model the provider reports.
215    pub model: Option<String>,
216    /// The provider's report that the turn failed, such as a refusal's
217    /// explanation. The turn is then never replayed.
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub error: Option<String>,
220}
221
222/// What a provider item becomes when it opens.
223#[derive(Debug, Clone, PartialEq)]
224pub enum Block {
225    /// Answer text.
226    Text,
227    /// Reasoning, possibly withheld by the provider.
228    Reasoning {
229        /// Whether the provider withheld the text.
230        redacted: bool,
231    },
232    /// An image, its canonical fields stated at open; fragments append
233    /// base64 data.
234    Image(Image),
235    /// A tool call with its id and name.
236    Call {
237        /// The call's id.
238        id: CallId,
239        /// The tool's name.
240        name: ToolName,
241    },
242    /// An item with no canonical meaning.
243    Opaque {
244        /// Whether the item goes back to the model that produced it.
245        replay: bool,
246    },
247}
248
249/// The completion fold. The driver and the handler writer build it; no other
250/// code can feed one.
251///
252/// It is both sides of one reply: the writer state a decoder writes through
253/// (the open items by wire index) and the blocks the consumer has taken, in
254/// their position.
255pub struct ReplyFold {
256    span: tracing::Span,
257    /// Who the reply is from; the end adds the provider's model and id.
258    origin: Origin,
259    /// The facts of the wire that answers, which price the reply.
260    facts: crate::catalog::ModelFacts,
261    // The writer.
262    open: BTreeMap<usize, Draft>,
263    /// The position of the block each wire index last closed, whose item
264    /// [`Out::edit`] may still amend until the reply ends.
265    ended: HashMap<usize, usize>,
266    next_part: u32,
267    call_ids: HashSet<CallId>,
268    /// Whether the reply's end must name a finish reason: a wire's fold
269    /// whose target states one (a relayed or written reply states its own).
270    wire: bool,
271    /// Where the wire's call items hold their id ([`ReplayTarget::call_id_slot`]).
272    ///
273    /// [`ReplayTarget::call_id_slot`]: crate::completion::ReplayTarget::call_id_slot
274    call_id_slot: Option<&'static str>,
275    /// Whether the request accepts an unknown finish reason as a normal stop
276    /// ([`CompletionRequest::accept_unknown_finish_reasons`]).
277    ///
278    /// [`CompletionRequest::accept_unknown_finish_reasons`]: field@CompletionRequest::accept_unknown_finish_reasons
279    accept_unknown_finish: bool,
280    /// The first position whose item closed without the provider stating it
281    /// complete. An item there may be the partner a later one needs, so
282    /// blocks from it on replay from their canonical fields.
283    first_incomplete: Option<usize>,
284    /// The calls still open when the provider ended the reply, in the
285    /// order they opened ([`CompletionResponse::unfinished_calls`]).
286    unfinished: Vec<CallId>,
287    /// The call the last index-less fragment went to.
288    last_call: Option<usize>,
289    /// The index of the block a boundary-less wire is streaming.
290    run: Option<usize>,
291    next_auto: usize,
292    /// The wire index of each position, for a wire whose choice follows
293    /// its indices ([`Out::order_by_index`]).
294    by_index: Option<BTreeMap<usize, usize>>,
295    /// The position of the block the choice holds first ([`Out::lead`]).
296    lead: Option<usize>,
297    // The fold.
298    choice: Vec<Option<AssistantContent>>,
299    /// The text the consumer took of text and reasoning parts still open,
300    /// by position.
301    open_text: BTreeMap<usize, AssistantContent>,
302}
303
304/// One open provider item.
305struct Draft {
306    part: Part,
307    /// Whether its start was emitted.
308    started: bool,
309    /// The provider's item as assembled so far; `Null` for none.
310    item: serde_json::Value,
311    body: Body,
312    /// The citations of a text item, resolved when it closes.
313    citations: Vec<WireCitation>,
314}
315
316/// Whether a closing item keeps its provider item as the block's native.
317#[derive(Clone, Copy, PartialEq, Eq)]
318enum Closing {
319    /// The provider stated the item complete: it becomes the native.
320    Complete,
321    /// The item never completed: the block has no native, and an opaque
322    /// item does not replay.
323    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/// A tool call's argument text as it arrives.
356#[derive(Default)]
357struct Arguments {
358    text: String,
359    /// How many bytes of `text` the stream has carried.
360    streamed: usize,
361    overflowed: bool,
362    /// Whether any fragment carried a non-blank byte.
363    substantive: bool,
364    /// Arguments the provider announced when the call opened, used only
365    /// when no fragment arrives.
366    announced: Option<serde_json::Value>,
367}
368
369/// The most argument bytes one call accumulates.
370const 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        // Some OpenAI-compatible gateways send a literal `null` before the
376        // real fragments; a non-blank fragment supersedes it.
377        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    /// The call to `name` these arguments make: the announced ones when no
394    /// fragment arrived, else the text, read by [`ToolFunction::parse`].
395    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    /// The text the stream has not carried yet, now marked carried. A text
409    /// that may still be a gateway's placeholder `null` waits, since the
410    /// first real fragment replaces it.
411    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    /// Whether the text is a complete JSON object.
425    fn complete(&self) -> bool {
426        !self.overflowed && crate::json_utils::is_whole_object(&self.text)
427    }
428}
429
430/// Whether `id` names a call: some providers send `""` or `"null"` for a
431/// call they gave no id.
432fn stated(id: &str) -> bool {
433    !id.is_empty() && id != "null"
434}
435
436/// What one fragment of a buffered tool call carries.
437#[derive(Debug, Clone, Copy, Default)]
438pub struct CallFragment<'a> {
439    /// The provider's id for the call.
440    pub id: Option<&'a str>,
441    /// The tool's name.
442    pub name: Option<&'a str>,
443    /// A fragment of the argument JSON.
444    pub arguments: Option<&'a str>,
445}
446
447/// The first index the writer hands out itself ([`Out::fresh_index`]); a
448/// provider's wire indices stay below it.
449pub 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
457/// Carry what the open call `draft` has not streamed yet: its start once it
458/// names a tool, then the argument text since the last fragment. A call
459/// that never names a tool stays unseen, as its close drops it.
460fn 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    /// The writer of a reply from `origin`.
494    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    /// The fold of a stream relayed from another fold, which built its
519    /// events: it only collects them, and takes its origin from the relay
520    /// ([`Self::set_origin`]). Until then it names the relay `label`.
521    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    /// Who the reply is from, as the relay or the writer's owner states it.
527    pub(crate) fn set_origin(&mut self, origin: Origin) {
528        self.origin = origin;
529    }
530
531    /// The next position in the choice.
532    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    /// The id a call closing now takes, and the provider item it keeps:
661    /// `id`, or, when the provider sent none or one an earlier call of the
662    /// reply holds, a fresh rig-issued id. A renamed call
663    /// keeps no item, since the item names the id it lost; a call sent with
664    /// no id keeps its item only on a wire whose items have an id slot,
665    /// which replay then fills with the id its result gets.
666    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    /// Close the item at `index`: its block becomes visible, holding the
685    /// provider's item as its native when `closing` says it is complete.
686    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    /// [`Self::close_item`], saying the id of the call it closed, if it
696    /// closed one.
697    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        // A call streams what it has not yet: its start, if it names a tool,
705        // and the argument text since its last fragment.
706        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                // Resolved at close: a provider may cite a text before its
731                // last fragment arrives.
732                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                // A call that streamed no text (none arrived, or only a
773                // placeholder `null`) states the arguments it ends with.
774                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    /// Edit the item of the block the wire index `index` last closed, while
799    /// its end event waits to be taken or once the fold holds it.
800    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    /// Add `citations` to the text item at `index`, or with `replace` put
824    /// them in place of its own. An open item resolves them when it closes;
825    /// a closed one, against its final text now, while its end event waits
826    /// to be taken or once the fold holds it. Citations for any other item
827    /// are dropped with a warning.
828    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    /// One whole block at the next position, its native kept as given.
928    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    /// Close every item still open when the provider ends the reply, in the
987    /// order they opened. The block a boundary-less wire was streaming is
988    /// complete; any other item was never stated complete, so its block has
989    /// no native. Calls keep what their arguments state.
990    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    /// Record that the item at `position` never completed.
1012    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    /// Who the reply is from.
1020    pub fn origin(&self) -> &Origin {
1021        &self.origin
1022    }
1023
1024    /// What arrived so far as a response: every part that ended, the text
1025    /// the consumer already took of a text or reasoning part still open,
1026    /// and the provider's end when it arrived. A reply the provider did not
1027    /// end is a failed turn: `failure` is the error that ended it, and
1028    /// without one the caller stopped reading. Unless the provider ended the
1029    /// reply and the consumer took every part, no block keeps its provider
1030    /// item.
1031    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        // A turn the consumer has not wholly taken replays only its canonical
1052        // fields, as pi drops an unfinished turn's provider data: no item of
1053        // it can then need a partner that never arrived.
1054        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    /// `parts` by position, in wire-index order on a wire that asked for
1075    /// it and after the lead block, each from position `canonical_from` on
1076    /// with no provider item.
1077    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    /// The response of `choice`, the reply's parts, ended by `end`.
1107    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        // A request that named no model is from the model the provider
1124        // reports.
1125        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
1155/// `usage` with its cost: the one the provider reported, else the counters
1156/// priced at the requested model's pricing in `facts`, else none.
1157fn 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
1165/// The providers whose replies the catalog does not price: ChatGPT and
1166/// Copilot bill a subscription, not the per-token API rates the catalog
1167/// lists, and an Ollama server is usually local, while the catalog's
1168/// `ollama` prices are Ollama Cloud's and the reply does not say which
1169/// served it.
1170const UNPRICED: [&str; 3] = [
1171    crate::providers::chatgpt::PROVIDER_NAME,
1172    crate::providers::copilot::PROVIDER_NAME,
1173    crate::providers::ollama::PROVIDER_NAME,
1174];
1175
1176/// What `usage` costs at the pricing `facts` give `origin`'s model (the
1177/// spec the wire was connected to, else its catalog's entry), or `None`
1178/// when they have no price for it or the provider is one the catalog does
1179/// not price ([`UNPRICED`]).
1180fn 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
1194/// `block` with no provider item: an opaque item no longer replays.
1195pub(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
1205/// The provider item `content` holds, if any.
1206fn 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
1217/// `block` holding `item` as its native, unless there is no item.
1218fn 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
1242/// Merge `delta` into `item`: each string field but `type` appends to the
1243/// item's string of that key, an array extends the item's array, and any
1244/// other value replaces the key. A delta kind rig has never seen still
1245/// lands in the item.
1246pub 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        // The parts taken, in their position (in wire-index order on a wire
1322        // that asked for it, after the lead block); parts from the first
1323        // incomplete one on keep no provider item.
1324        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    /// What `usage` costs at the requested model's pricing in the wire's
1335    /// facts, for a decoder whose provider bills other counters than the
1336    /// ones it reports as usage.
1337    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    /// Open the provider item at wire `index` as `block`: it takes the next
1343    /// position in the reply. `item` is the item as its first event states
1344    /// it, the base its deltas merge into; `Null` when the block keeps no
1345    /// provider item. An index already open is an error.
1346    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    /// Append a fragment to the open item at `index`: text, reasoning, or a
1358    /// call's argument JSON, by the item's block.
1359    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    /// Edit the item at `index` in place: apply a delta with [`merge`], or
1366    /// replace the whole item a provider restates when it finishes.
1367    /// Until the reply ends, an index whose block already closed edits that
1368    /// block's item: the terminal backfill of a field the provider states
1369    /// only at its end.
1370    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    /// Close the item at `index` the provider never stated complete: its
1387    /// block becomes visible with no native, and an opaque item is kept but
1388    /// does not replay. Empty text and reasoning are dropped, and so is a
1389    /// call with no name. A call's arguments are read by
1390    /// [`ToolFunction::parse`], so malformed ones never fail the reply; a
1391    /// call that never got an id, or reuses one an earlier call took, gets
1392    /// one rig issues.
1393    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    /// Close the item at `index` the provider stated complete: [`Self::close`],
1400    /// with the item as assembled becoming the block's native.
1401    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    /// End every item still open as stated complete, for a wire whose end
1408    /// of reply is the provider's statement that its items are done.
1409    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    /// Open and finish the item at `index` in one step: a whole block a
1425    /// provider states in one piece, `item` its provider item and `text`
1426    /// its text, reasoning, argument JSON or base64 image data.
1427    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    /// Order the response's blocks by wire index rather than by when they
1440    /// opened, for a wire whose indices are the provider's item order and
1441    /// whose end may state items it never streamed. Call it before the
1442    /// first item opens; blocks with no wire index go last.
1443    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    /// Hold the open item at `index` first in the response whenever it
1451    /// opened, for a wire that states one block of the turn apart from its
1452    /// order. Its events keep their arrival order. A turn leads with the
1453    /// first item named.
1454    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    /// Replace the text, reasoning or argument JSON of the open item at
1462    /// `index` with `text`, the whole of it as the provider restates it at
1463    /// its end. The fragments already streamed stand and nothing more
1464    /// streams for them; the block ends holding `text`.
1465    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                // A call not yet started streams the restated text whole.
1473                arguments.streamed = if draft.started { text.len() } else { 0 };
1474            }
1475            Body::Image(_) | Body::Opaque { .. } => {}
1476        }
1477        Ok(())
1478    }
1479
1480    /// Write one fragment of a tool call the provider streams, opening the
1481    /// call at its first fragment. Its id and name may arrive in any
1482    /// fragment, and the first name it is given stays: the call starts
1483    /// streaming once it names a tool, carrying
1484    /// the argument text held until then, and its end states its id. Calls
1485    /// are told apart by `index` when the wire gives one. A new id under an
1486    /// index starts a new call once the held call's arguments are a complete
1487    /// object or when it names a tool: some providers send a fresh id, and
1488    /// no name, with every chunk of one call. Without an index (`None`, or
1489    /// the wire's `null`), a fragment with an unseen id opens a call, and one
1490    /// with no id continues the latest call while its arguments are
1491    /// incomplete.
1492    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                    // Another call took over the index: the one it held ends.
1515                    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        // A call keeps the first name it is given, so the tool its start
1572        // names is the tool its end names.
1573        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    /// Arguments the provider announced for the call at `index`, used only
1586    /// if no fragment arrives.
1587    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    /// An index no provider item uses, for a wire that indexes nothing.
1599    pub fn fresh_index(&mut self) -> usize {
1600        self.lock().fold.fresh_index()
1601    }
1602
1603    /// A fragment on a wire that marks no item boundaries: it continues the
1604    /// block the last fragment went to while `block` is the same kind, and
1605    /// otherwise closes that block and opens a new one. Returns the block's
1606    /// index, for merging its provider item.
1607    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    /// Close the block [`Self::run`] is extending, if any: output of
1614    /// another kind interleaved it.
1615    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    /// Add `citation` to the text item at wire `index`, open or ended. Its
1622    /// span is resolved to bytes of the text when the item closes, or at
1623    /// once for an item already closed. A span that does not resolve, or
1624    /// covers other text than its `quoted`, drops the citation with a
1625    /// warning, as do citations of an item that is not text: a citation
1626    /// never fails a reply.
1627    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    /// Put `citations` in place of the text item's own, for a provider that
1634    /// restates them; [`Self::cite`] otherwise.
1635    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    /// A whole block of an already assembled response, at the next
1642    /// position, its native kept as given.
1643    #[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
1651/// The events a stream of `response` would have carried: each part whole,
1652/// in order.
1653pub(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;