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, &reply.raw);
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(
1158    usage: Usage,
1159    origin: &Origin,
1160    facts: &crate::catalog::ModelFacts,
1161    raw: &serde_json::Value,
1162) -> Usage {
1163    if usage.cost.is_some() {
1164        return usage;
1165    }
1166    let cost = catalog_cost(origin, facts, &usage, raw);
1167    usage.cost(cost)
1168}
1169
1170/// The providers whose replies the catalog does not price: ChatGPT and
1171/// Copilot bill a subscription, not the per-token API rates the catalog
1172/// lists, and an Ollama server is usually local, while the catalog's
1173/// `ollama` prices are Ollama Cloud's and the reply does not say which
1174/// served it.
1175const UNPRICED: [&str; 3] = [
1176    crate::providers::chatgpt::PROVIDER_NAME,
1177    crate::providers::copilot::PROVIDER_NAME,
1178    crate::providers::ollama::PROVIDER_NAME,
1179];
1180
1181/// What `usage` costs at the pricing `facts` give `origin`'s model (the
1182/// spec the wire was connected to, else its catalog's entry), or `None`
1183/// when they have no price for it or the provider is one the catalog does
1184/// not price ([`UNPRICED`]).
1185fn catalog_cost(
1186    origin: &Origin,
1187    facts: &crate::catalog::ModelFacts,
1188    usage: &Usage,
1189    raw: &serde_json::Value,
1190) -> Option<crate::completion::Cost> {
1191    if UNPRICED.contains(&origin.provider.as_str()) {
1192        return None;
1193    }
1194    let pricing = facts
1195        .for_model(&origin.provider, &origin.model)?
1196        .pricing
1197        .as_ref()?;
1198    if origin.provider == crate::providers::anthropic::PROVIDER_NAME
1199        && origin.api.as_str() == crate::providers::anthropic::wire::MESSAGES_API
1200    {
1201        return pricing.cost_with_cache_write(usage, anthropic_write_cost(pricing, usage, raw));
1202    }
1203    pricing.cost(usage)
1204}
1205
1206/// Native Anthropic writes use the reported TTL split. One-hour writes
1207/// cost twice the input rate; the catalog's write rate is for five minutes.
1208/// These are standard API list prices, not subscription charges or a
1209/// custom endpoint's invoice.
1210fn anthropic_write_cost(
1211    pricing: &crate::catalog::Pricing,
1212    usage: &Usage,
1213    raw: &serde_json::Value,
1214) -> Option<f64> {
1215    let written = usage.cache_creation_input_tokens.unwrap_or(0);
1216    if written == 0 {
1217        return Some(0.0);
1218    }
1219    let split = raw.pointer("/usage/cache_creation")?;
1220    let short = split.get("ephemeral_5m_input_tokens")?.as_u64()?;
1221    let long = split.get("ephemeral_1h_input_tokens")?.as_u64()?;
1222    if short.checked_add(long)? != written {
1223        return None;
1224    }
1225    let short_cost = if short == 0 {
1226        0.0
1227    } else {
1228        short as f64 * pricing.cache_write?
1229    };
1230    Some((short_cost + long as f64 * pricing.input * 2.0) / 1_000_000.0)
1231}
1232
1233/// `block` with no provider item: an opaque item no longer replays.
1234pub(crate) fn canonical(block: AssistantContent) -> AssistantContent {
1235    match block {
1236        AssistantContent::Opaque(opaque) => AssistantContent::Opaque(Opaque {
1237            replay: false,
1238            ..opaque
1239        }),
1240        block => block.into_canonical(),
1241    }
1242}
1243
1244/// The provider item `content` holds, if any.
1245fn item_of(content: &mut AssistantContent) -> Option<&mut serde_json::Value> {
1246    let native = match content {
1247        AssistantContent::Text(text) => text.native.as_mut(),
1248        AssistantContent::ToolCall(call) => call.native.as_mut(),
1249        AssistantContent::Reasoning(reasoning) => reasoning.native.as_mut(),
1250        AssistantContent::Image(image) => image.native.as_mut(),
1251        AssistantContent::Opaque(opaque) => return Some(&mut opaque.item),
1252    };
1253    native.map(|native| &mut native.item)
1254}
1255
1256/// `block` holding `item` as its native, unless there is no item.
1257fn with_item(block: AssistantContent, item: serde_json::Value) -> AssistantContent {
1258    if item.is_null() {
1259        block
1260    } else {
1261        block.with_native(item)
1262    }
1263}
1264
1265fn kind_of(content: &AssistantContent) -> PartKind {
1266    match content {
1267        AssistantContent::Text(_) => PartKind::Text,
1268        AssistantContent::Reasoning(_) => PartKind::Reasoning,
1269        AssistantContent::ToolCall(_) => PartKind::ToolCall,
1270        AssistantContent::Image(_) => PartKind::Image,
1271        AssistantContent::Opaque(_) => PartKind::Opaque,
1272    }
1273}
1274
1275pub(crate) type Items = std::collections::VecDeque<Result<Item<StreamEvent>, ProviderError>>;
1276
1277fn emit(items: &mut Items, event: StreamEvent) {
1278    items.push_back(Ok(Item::Event(event)));
1279}
1280
1281/// Merge `delta` into `item`: each string field but `type` appends to the
1282/// item's string of that key, an array extends the item's array, and any
1283/// other value replaces the key. A delta kind rig has never seen still
1284/// lands in the item.
1285pub fn merge(item: &mut serde_json::Value, delta: &serde_json::Map<String, serde_json::Value>) {
1286    use serde_json::Value;
1287    if !item.is_object() {
1288        *item = Value::Object(serde_json::Map::new());
1289    }
1290    let Value::Object(item) = item else {
1291        return;
1292    };
1293    for (key, value) in delta {
1294        if key == "type" {
1295            continue;
1296        }
1297        match (item.get_mut(key), value) {
1298            (Some(Value::String(existing)), Value::String(fragment)) => {
1299                existing.push_str(fragment);
1300            }
1301            (Some(Value::Array(existing)), Value::Array(more)) => {
1302                existing.extend(more.iter().cloned());
1303            }
1304            _ => {
1305                item.insert(key.clone(), value.clone());
1306            }
1307        }
1308    }
1309}
1310
1311impl Fold<Completion> for ReplyFold {
1312    fn absorb(&mut self, event: &StreamEvent) -> Result<(), ProviderError> {
1313        match event {
1314            StreamEvent::Start { part, .. } => {
1315                if self.choice.len() <= part.index() {
1316                    self.choice.resize(part.index() + 1, None);
1317                }
1318            }
1319            StreamEvent::End { part, content } => {
1320                if self.choice.len() <= part.index() {
1321                    self.choice.resize(part.index() + 1, None);
1322                }
1323                if let Some(slot) = self.choice.get_mut(part.index()) {
1324                    *slot = Some(content.clone());
1325                }
1326                self.open_text.remove(&part.index());
1327            }
1328            StreamEvent::Text { part, text } => {
1329                if let AssistantContent::Text(open) = self
1330                    .open_text
1331                    .entry(part.index())
1332                    .or_insert_with(|| AssistantContent::text(""))
1333                {
1334                    open.text.push_str(text);
1335                }
1336            }
1337            StreamEvent::Reasoning { part, text } => {
1338                if let AssistantContent::Reasoning(open) = self
1339                    .open_text
1340                    .entry(part.index())
1341                    .or_insert_with(|| AssistantContent::reasoning(""))
1342                {
1343                    open.text.push_str(text);
1344                }
1345            }
1346            StreamEvent::Arguments { .. } => {}
1347        }
1348        Ok(())
1349    }
1350
1351    fn finish(mut self, end: Finish, reply: Reply) -> Result<CompletionResponse, ProviderError> {
1352        if let Some(FinishReason::Other(reason)) = &end.reason {
1353            tracing::warn!(
1354                provider = %self.origin.provider,
1355                reason = %reason,
1356                accepted = self.accept_unknown_finish,
1357                "the provider ended the reply with an unknown finish reason"
1358            );
1359        }
1360        // The parts taken, in their position (in wire-index order on a wire
1361        // that asked for it, after the lead block); parts from the first
1362        // incomplete one on keep no provider item.
1363        let parts = std::mem::take(&mut self.choice).into_iter().enumerate();
1364        let choice = self.ordered(parts.collect(), self.first_incomplete);
1365        let response = self.response(end, reply, choice);
1366        self.span
1367            .record_response(response.response_id(), response.model(), &response.usage);
1368        Ok(response)
1369    }
1370}
1371
1372impl<'id> Out<'id, Completion> {
1373    /// What `usage` costs at the requested model's pricing in the wire's
1374    /// facts, for a decoder whose provider bills other counters than the
1375    /// ones it reports as usage.
1376    pub(crate) fn catalog_cost(&self, usage: &Usage) -> Option<crate::completion::Cost> {
1377        let shared = self.lock();
1378        catalog_cost(
1379            &shared.fold.origin,
1380            &shared.fold.facts,
1381            usage,
1382            &serde_json::Value::Null,
1383        )
1384    }
1385
1386    /// Open the provider item at wire `index` as `block`: it takes the next
1387    /// position in the reply. `item` is the item as its first event states
1388    /// it, the base its deltas merge into; `Null` when the block keeps no
1389    /// provider item. An index already open is an error.
1390    pub fn open(
1391        &mut self,
1392        index: usize,
1393        block: Block,
1394        item: serde_json::Value,
1395    ) -> Result<(), ProviderError> {
1396        let mut shared = self.lock();
1397        let Shared { fold, items, .. } = &mut *shared;
1398        fold.open_item(items, index, block, item)
1399    }
1400
1401    /// Append a fragment to the open item at `index`: text, reasoning, or a
1402    /// call's argument JSON, by the item's block.
1403    pub fn push(&mut self, index: usize, fragment: &str) -> Result<(), ProviderError> {
1404        let mut shared = self.lock();
1405        let Shared { fold, items, .. } = &mut *shared;
1406        fold.push_item(items, index, fragment)
1407    }
1408
1409    /// Edit the item at `index` in place: apply a delta with [`merge`], or
1410    /// replace the whole item a provider restates when it finishes.
1411    /// Until the reply ends, an index whose block already closed edits that
1412    /// block's item: the terminal backfill of a field the provider states
1413    /// only at its end.
1414    pub fn edit(
1415        &mut self,
1416        index: usize,
1417        edit: impl FnOnce(&mut serde_json::Value),
1418    ) -> Result<(), ProviderError> {
1419        let mut shared = self.lock();
1420        let Shared { fold, items, .. } = &mut *shared;
1421        match fold.open.get_mut(&index) {
1422            Some(draft) => {
1423                edit(&mut draft.item);
1424                Ok(())
1425            }
1426            None => fold.edit_ended(items, index, edit),
1427        }
1428    }
1429
1430    /// Close the item at `index` the provider never stated complete: its
1431    /// block becomes visible with no native, and an opaque item is kept but
1432    /// does not replay. Empty text and reasoning are dropped, and so is a
1433    /// call with no name. A call's arguments are read by
1434    /// [`ToolFunction::parse`], so malformed ones never fail the reply; a
1435    /// call that never got an id, or reuses one an earlier call took, gets
1436    /// one rig issues.
1437    pub fn close(&mut self, index: usize) -> Result<(), ProviderError> {
1438        let mut shared = self.lock();
1439        let Shared { fold, items, .. } = &mut *shared;
1440        fold.close_item(items, index, Closing::Incomplete)
1441    }
1442
1443    /// Close the item at `index` the provider stated complete: [`Self::close`],
1444    /// with the item as assembled becoming the block's native.
1445    pub fn finish(&mut self, index: usize) -> Result<(), ProviderError> {
1446        let mut shared = self.lock();
1447        let Shared { fold, items, .. } = &mut *shared;
1448        fold.close_item(items, index, Closing::Complete)
1449    }
1450
1451    /// End every item still open as stated complete, for a wire whose end
1452    /// of reply is the provider's statement that its items are done.
1453    pub fn finish_open(&mut self) -> Result<(), ProviderError> {
1454        let mut shared = self.lock();
1455        let Shared { fold, items, .. } = &mut *shared;
1456        let mut open: Vec<(Part, usize)> = fold
1457            .open
1458            .iter()
1459            .map(|(index, draft)| (draft.part, *index))
1460            .collect();
1461        open.sort();
1462        for (_, index) in open {
1463            fold.close_item(items, index, Closing::Complete)?;
1464        }
1465        Ok(())
1466    }
1467
1468    /// Open and finish the item at `index` in one step: a whole block a
1469    /// provider states in one piece, `item` its provider item and `text`
1470    /// its text, reasoning, argument JSON or base64 image data.
1471    pub fn whole(
1472        &mut self,
1473        index: usize,
1474        block: Block,
1475        item: serde_json::Value,
1476        text: &str,
1477    ) -> Result<(), ProviderError> {
1478        self.open(index, block, item)?;
1479        self.push(index, text)?;
1480        self.finish(index)
1481    }
1482
1483    /// Order the response's blocks by wire index rather than by when they
1484    /// opened, for a wire whose indices are the provider's item order and
1485    /// whose end may state items it never streamed. Call it before the
1486    /// first item opens; blocks with no wire index go last.
1487    pub fn order_by_index(&mut self) {
1488        let mut shared = self.lock();
1489        if shared.fold.by_index.is_none() {
1490            shared.fold.by_index = Some(BTreeMap::new());
1491        }
1492    }
1493
1494    /// Hold the open item at `index` first in the response whenever it
1495    /// opened, for a wire that states one block of the turn apart from its
1496    /// order. Its events keep their arrival order. A turn leads with the
1497    /// first item named.
1498    pub fn lead(&mut self, index: usize) -> Result<(), ProviderError> {
1499        let mut shared = self.lock();
1500        let position = shared.fold.draft(index)?.part.index();
1501        shared.fold.lead.get_or_insert(position);
1502        Ok(())
1503    }
1504
1505    /// Replace the text, reasoning or argument JSON of the open item at
1506    /// `index` with `text`, the whole of it as the provider restates it at
1507    /// its end. The fragments already streamed stand and nothing more
1508    /// streams for them; the block ends holding `text`.
1509    pub fn restate(&mut self, index: usize, text: &str) -> Result<(), ProviderError> {
1510        let mut shared = self.lock();
1511        let draft = shared.fold.draft(index)?;
1512        match &mut draft.body {
1513            Body::Text(body) | Body::Reasoning { text: body, .. } => text.clone_into(body),
1514            Body::Call { arguments, .. } => {
1515                text.clone_into(&mut arguments.text);
1516                // A call not yet started streams the restated text whole.
1517                arguments.streamed = if draft.started { text.len() } else { 0 };
1518            }
1519            Body::Image(_) | Body::Opaque { .. } => {}
1520        }
1521        Ok(())
1522    }
1523
1524    /// Write one fragment of a tool call the provider streams, opening the
1525    /// call at its first fragment. Its id and name may arrive in any
1526    /// fragment, and the first name it is given stays: the call starts
1527    /// streaming once it names a tool, carrying
1528    /// the argument text held until then, and its end states its id. Calls
1529    /// are told apart by `index` when the wire gives one. A new id under an
1530    /// index starts a new call once the held call's arguments are a complete
1531    /// object or when it names a tool: some providers send a fresh id, and
1532    /// no name, with every chunk of one call. Without an index (`None`, or
1533    /// the wire's `null`), a fragment with an unseen id opens a call, and one
1534    /// with no id continues the latest call while its arguments are
1535    /// incomplete.
1536    pub fn fragment(
1537        &mut self,
1538        index: Option<usize>,
1539        fragment: CallFragment<'_>,
1540    ) -> Result<(), ProviderError> {
1541        let mut shared = self.lock();
1542        let Shared {
1543            fold: turn, items, ..
1544        } = &mut *shared;
1545        let new_id = fragment.id.filter(|id| stated(id));
1546        let index = match index {
1547            Some(index) => {
1548                let names = fragment.name.is_some_and(|name| !name.is_empty());
1549                let held = turn.open.get(&index).and_then(|draft| match &draft.body {
1550                    Body::Call {
1551                        id: Some(id),
1552                        arguments,
1553                        ..
1554                    } if new_id.is_some_and(|new| id.wire() != new) => Some(arguments.complete()),
1555                    _ => None,
1556                });
1557                if let Some(complete) = held.filter(|complete| *complete || names) {
1558                    // Another call took over the index: the one it held ends.
1559                    let moved = turn.fresh_index();
1560                    if let Some(draft) = turn.open.remove(&index) {
1561                        turn.open.insert(moved, draft);
1562                    }
1563                    let closing = if complete {
1564                        Closing::Complete
1565                    } else {
1566                        Closing::Incomplete
1567                    };
1568                    turn.close_item(items, moved, closing)?;
1569                }
1570                index
1571            }
1572            None => {
1573                let owner = new_id.and_then(|new| {
1574                    turn.open
1575                        .iter()
1576                        .find_map(|(index, draft)| match &draft.body {
1577                            Body::Call { id: Some(id), .. } if id.wire() == new => Some(*index),
1578                            _ => None,
1579                        })
1580                });
1581                let continues = |last: &usize| {
1582                    turn.open.get(last).is_some_and(|draft| {
1583                        matches!(&draft.body, Body::Call { arguments, .. } if !arguments.complete())
1584                    })
1585                };
1586                match (owner, new_id, turn.last_call) {
1587                    (Some(index), _, _) => index,
1588                    (None, None, Some(last)) if continues(&last) => last,
1589                    _ => turn.fresh_index(),
1590                }
1591            }
1592        };
1593        turn.last_call = Some(index);
1594        if !turn.open.contains_key(&index) {
1595            let body = Body::Call {
1596                id: None,
1597                name: String::new(),
1598                arguments: Arguments::default(),
1599            };
1600            turn.insert(index, body, serde_json::Value::Null);
1601        }
1602        let Body::Call {
1603            id,
1604            name,
1605            arguments,
1606        } = &mut turn.draft(index)?.body
1607        else {
1608            return Err(ProviderError::Response(format!(
1609                "the reply sent a call fragment for item {index}, which is not a call"
1610            )));
1611        };
1612        if let Some(call_id) = fragment.id.filter(|id| stated(id)) {
1613            *id = Some(CallId::from_wire(call_id));
1614        }
1615        // A call keeps the first name it is given, so the tool its start
1616        // names is the tool its end names.
1617        if name.is_empty()
1618            && let Some(fragment) = fragment.name.filter(|name| !name.is_empty())
1619        {
1620            fragment.clone_into(name);
1621        }
1622        if let Some(fragment) = fragment.arguments {
1623            arguments.push(fragment, name);
1624        }
1625        stream_call(items, turn.draft(index)?);
1626        Ok(())
1627    }
1628
1629    /// Arguments the provider announced for the call at `index`, used only
1630    /// if no fragment arrives.
1631    pub fn announce(
1632        &mut self,
1633        index: usize,
1634        announced: serde_json::Value,
1635    ) -> Result<(), ProviderError> {
1636        if let Body::Call { arguments, .. } = &mut self.lock().fold.draft(index)?.body {
1637            arguments.announced = Some(announced);
1638        }
1639        Ok(())
1640    }
1641
1642    /// An index no provider item uses, for a wire that indexes nothing.
1643    pub fn fresh_index(&mut self) -> usize {
1644        self.lock().fold.fresh_index()
1645    }
1646
1647    /// A fragment on a wire that marks no item boundaries: it continues the
1648    /// block the last fragment went to while `block` is the same kind, and
1649    /// otherwise closes that block and opens a new one. Returns the block's
1650    /// index, for merging its provider item.
1651    pub fn run(&mut self, block: Block, fragment: &str) -> Result<usize, ProviderError> {
1652        let mut shared = self.lock();
1653        let Shared { fold, items, .. } = &mut *shared;
1654        fold.run_item(items, block, fragment)
1655    }
1656
1657    /// Close the block [`Self::run`] is extending, if any: output of
1658    /// another kind interleaved it.
1659    pub fn end_run(&mut self) -> Result<(), ProviderError> {
1660        let mut shared = self.lock();
1661        let Shared { fold, items, .. } = &mut *shared;
1662        fold.end_run(items)
1663    }
1664
1665    /// Add `citation` to the text item at wire `index`, open or ended. Its
1666    /// span is resolved to bytes of the text when the item closes, or at
1667    /// once for an item already closed. A span that does not resolve, or
1668    /// covers other text than its `quoted`, drops the citation with a
1669    /// warning, as do citations of an item that is not text: a citation
1670    /// never fails a reply.
1671    pub fn cite(&mut self, index: usize, citation: WireCitation) {
1672        let mut shared = self.lock();
1673        let Shared { fold, items, .. } = &mut *shared;
1674        fold.cite_item(items, index, vec![citation], false);
1675    }
1676
1677    /// Put `citations` in place of the text item's own, for a provider that
1678    /// restates them; [`Self::cite`] otherwise.
1679    pub fn set_citations(&mut self, index: usize, citations: Vec<WireCitation>) {
1680        let mut shared = self.lock();
1681        let Shared { fold, items, .. } = &mut *shared;
1682        fold.cite_item(items, index, citations, true);
1683    }
1684
1685    /// A whole block of an already assembled response, at the next
1686    /// position, its native kept as given.
1687    #[cfg(any(test, feature = "test-utils"))]
1688    pub(crate) fn content(&mut self, content: AssistantContent) -> Result<(), ProviderError> {
1689        let mut shared = self.lock();
1690        let Shared { fold, items, .. } = &mut *shared;
1691        fold.write_content(items, content)
1692    }
1693}
1694
1695/// The events a stream of `response` would have carried: each part whole,
1696/// in order.
1697pub(crate) fn events_of(
1698    response: &CompletionResponse,
1699) -> Result<Vec<Item<StreamEvent>>, ProviderError> {
1700    let mut turn = ReplyFold::new(response.origin.clone());
1701    let mut items = Items::new();
1702    for content in &response.choice {
1703        turn.write_content(&mut items, content.clone())?;
1704    }
1705    items.into_iter().collect()
1706}
1707
1708#[cfg(test)]
1709mod tests;