Skip to main content

rig_core/completion/
history.rs

1//! Shaping a conversation for the model it is sent to. [`adapt`] is the one
2//! place history meets its target: a turn the same model produced keeps its
3//! provider items, any other turn replays from its canonical fields, failed
4//! turns are skipped, and every tool call gets an answer.
5//!
6//! ```
7//! use rig_core::completion::CompletionRequest;
8//! use rig_core::completion::history::{Accepts, ReplayTarget, adapt};
9//! use rig_core::completion::options::{Mapping, OptionFields, OptionMap};
10//! use rig_core::message::{Api, Message};
11//!
12//! #[derive(Debug)]
13//! struct Target;
14//!
15//! impl ReplayTarget for Target {
16//!     fn api(&self) -> Api {
17//!         Api::from_static("example.chat")
18//!     }
19//!     fn provider(&self) -> &str {
20//!         "example"
21//!     }
22//!     fn model(&self) -> &str {
23//!         "example-1"
24//!     }
25//!     fn accepts(&self, _model: &str) -> Accepts {
26//!         Accepts::ALL
27//!     }
28//!     fn map_options(&self, _request: &CompletionRequest, fields: OptionFields<'_>) -> OptionMap {
29//!         let OptionFields {
30//!             reasoning,
31//!             cache,
32//!             service_tier,
33//!             verbosity,
34//!             parallel_tool_calls,
35//!             top_p,
36//!             seed,
37//!             stop,
38//!             cache_key,
39//!         } = fields;
40//!         let none = |set: bool| match set {
41//!             true => Mapping::unsupported("the example takes no options"),
42//!             false => Mapping::Nothing,
43//!         };
44//!         OptionMap {
45//!             reasoning: none(reasoning.is_some()),
46//!             cache: none(cache.is_some()),
47//!             service_tier: none(service_tier.is_some()),
48//!             verbosity: none(verbosity.is_some()),
49//!             parallel_tool_calls: none(parallel_tool_calls.is_some()),
50//!             top_p: none(top_p.is_some()),
51//!             seed: none(seed.is_some()),
52//!             stop: none(!stop.is_empty()),
53//!             cache_key: Mapping::unrouted(cache_key),
54//!         }
55//!     }
56//! }
57//!
58//! let history = vec![Message::user("hi"), Message::assistant("hello")];
59//! assert_eq!(adapt(&history, &Target), history);
60//! ```
61
62use std::borrow::Cow;
63use std::collections::{HashMap, HashSet};
64use std::sync::Arc;
65
66use base64::Engine as _;
67use base64::prelude::{BASE64_STANDARD, BASE64_STANDARD_NO_PAD};
68
69use crate::conversation::pairing::is_result;
70use crate::message::{
71    Api, AssistantContent, AssistantMessage, CallId, DocumentData, DocumentMediaType,
72    DocumentSourceKind, ImageMediaType, Message, Origin, StopReason, Text, ToolResult,
73    ToolResultContent, UserContent,
74};
75use crate::wasm_compat::WasmCompatSync;
76
77/// What replaces a user image for a model without image input, and another
78/// model's assistant image for a model that reads no images in assistant
79/// turns.
80pub const IMAGE_OMITTED: &str = "(image omitted: model does not support images)";
81
82/// What replaces a tool-result image for a model without image input.
83pub const TOOL_IMAGE_OMITTED: &str = "(tool image omitted: model does not support images)";
84
85/// What replaces an image the provider cannot receive in its form.
86pub const IMAGE_UNSENDABLE: &str = "(image omitted: the provider cannot receive it in this form)";
87
88/// What replaces audio the provider cannot receive.
89pub const AUDIO_UNSENDABLE: &str = "(audio omitted: the provider cannot receive it in this form)";
90
91/// What replaces video the provider cannot receive.
92pub const VIDEO_UNSENDABLE: &str = "(video omitted: the provider cannot receive it in this form)";
93
94/// What replaces a document the provider cannot receive, when it holds no
95/// text to send instead.
96pub const DOCUMENT_UNSENDABLE: &str =
97    "(document omitted: the provider cannot receive it in this form)";
98
99/// What a tool-result image becomes in the result when the image moves to
100/// the user message that follows.
101pub const TOOL_IMAGE_ATTACHED: &str = "(see attached image)";
102
103/// The text heading the user message that carries tool-result images.
104pub const TOOL_IMAGES_HEADING: &str = "Attached image(s) from tool result:";
105
106/// What a model reads, as replay sees it.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct Accepts {
109    /// Images in user messages.
110    pub user_images: bool,
111    /// Images in assistant turns another model produced.
112    pub assistant_images: bool,
113    /// Images inside tool results.
114    pub tool_result_images: bool,
115    /// Tool calls and their results.
116    pub tools: bool,
117    /// Another model's reasoning text as reasoning: the wire has a place for
118    /// reasoning that carries no provider item or signature, such as Chat
119    /// Completions' `reasoning_content` field. Read only for a request that
120    /// asks for it ([`CompletionRequest::foreign_reasoning`]).
121    ///
122    /// [`CompletionRequest::foreign_reasoning`]: field@crate::completion::CompletionRequest::foreign_reasoning
123    pub foreign_reasoning: bool,
124}
125
126impl Accepts {
127    /// Every kind of content. Another model's reasoning is replayed as plain
128    /// text (`foreign_reasoning` is off).
129    pub const ALL: Self = Self {
130        user_images: true,
131        assistant_images: true,
132        tool_result_images: true,
133        tools: true,
134        foreign_reasoning: false,
135    };
136
137    /// Text and tools, no images. Another model's reasoning is replayed as
138    /// plain text.
139    pub const TEXT: Self = Self {
140        user_images: false,
141        assistant_images: false,
142        tool_result_images: false,
143        tools: true,
144        foreign_reasoning: false,
145    };
146}
147
148/// Where an image sits in a history.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum Place {
151    /// In a user message.
152    User,
153    /// Inside a tool result.
154    ToolResult,
155    /// In an assistant turn another model produced.
156    Assistant,
157}
158
159/// One media part, as [`ReplayTarget::encodes`] sees it. Raw bytes are
160/// already base64, and an inline image already has the media type its bytes
161/// name.
162#[derive(Debug, Clone, Copy)]
163pub enum Media<'a> {
164    /// An image, and where it sits.
165    Image(&'a crate::message::Image, Place),
166    /// Audio in a user message.
167    Audio(&'a crate::message::Audio),
168    /// Video in a user message.
169    Video(&'a crate::message::Video),
170    /// A document in a user message.
171    Document(&'a crate::message::Document),
172}
173
174/// The model a request is sent to, as replay sees it. Every completion wire
175/// implements it.
176pub trait ReplayTarget: std::fmt::Debug + WasmCompatSync {
177    /// The wire format.
178    fn api(&self) -> Api;
179
180    /// The provider descriptor name.
181    fn provider(&self) -> &str;
182
183    /// The model id the wire addresses.
184    fn model(&self) -> &str;
185
186    /// What `model`, the model a request addresses on this wire, reads.
187    /// [`adapt`] downgrades everything else, so the encoder never sees it.
188    fn accepts(&self, model: &str) -> Accepts;
189
190    /// How this wire answers each option of `request`, whose resolved model
191    /// and typed fields an answer may depend on. No default: every
192    /// completion wire writes one, destructuring `fields` with no `..`, so a
193    /// new option fails to compile until the wire answers for it. An unset
194    /// option answers `Mapping::Nothing`, so wrap each answer in
195    /// `Mapping::of` (`Mapping::of_stop` for `stop`), as in
196    /// `seed: Mapping::of(seed, |_| Mapping::unsupported("no such field"))`;
197    /// any other answer for an unset option fails every request.
198    /// `Completion::prepare` reports each refusal before the wire encodes.
199    fn map_options(
200        &self,
201        request: &crate::completion::CompletionRequest,
202        fields: crate::completion::options::OptionFields<'_>,
203    ) -> crate::completion::options::OptionMap;
204
205    /// The model facts this wire encodes with: the spec it was connected
206    /// to and the catalog that answers for other models. `None`, the
207    /// default, answers from the built-in catalog.
208    fn facts(&self) -> Option<&crate::catalog::ModelFacts> {
209        None
210    }
211
212    /// Whether the encoder carries `media` to `model`: its source (data,
213    /// URL, file id or string), its media type, and where it sits. [`adapt`]
214    /// replaces every part this refuses with a placeholder, or a text
215    /// document with its text, so the encoder never refuses canonical
216    /// content. Row H9 of the history conformance suite holds every wire to
217    /// it. By default every form is carried.
218    fn encodes(&self, model: &str, media: Media<'_>) -> bool {
219        let _ = (model, media);
220        true
221    }
222
223    /// The id `id` takes when a call another model made is sent to `model`
224    /// on this wire. `source` is the call's origin, `None` for a hand-built
225    /// turn. Results answering the call are rewritten to match, and an id
226    /// another call already took is made distinct.
227    fn normalize_tool_call_id(&self, id: &str, model: &str, source: Option<&Origin>) -> String {
228        let _ = (model, source);
229        id.to_owned()
230    }
231
232    /// Whether `request` continues a conversation the provider stores, such
233    /// as one naming a previous interaction. Results before the history's
234    /// first turn then answer calls the provider holds, so [`adapt`] keeps
235    /// them.
236    fn continues_stored(&self, request: &crate::completion::CompletionRequest) -> bool {
237        let _ = request;
238        false
239    }
240
241    /// Whether `request` declares tools to the provider, in `tools`, in the
242    /// `tools` of its `additional_params`, or through tools the wire adds.
243    /// A request that declares none gets its history's calls and results as
244    /// text. `ToolChoice::None` still declares them: the wire sends its own
245    /// `none` choice beside the tool history.
246    fn declares_tools(&self, request: &crate::completion::CompletionRequest) -> bool {
247        declares_tools(request)
248    }
249
250    /// The keys of a provider item that survive an edit of its block: an
251    /// encoder rebuilding the edited block keeps them ([`Replay::Identity`]).
252    /// By default none do.
253    fn identity(&self, item: &serde_json::Value) -> serde_json::Map<String, serde_json::Value> {
254        let _ = item;
255        serde_json::Map::new()
256    }
257
258    /// Where a tool call's id sits in this wire's call item, as a JSON
259    /// pointer, when it has one. Replay writes the call's wire id there, so
260    /// a replayed item and its result always agree, and a call the provider
261    /// sent without an id keeps its item only on a wire with a slot.
262    fn call_id_slot(&self) -> Option<&'static str> {
263        None
264    }
265
266    /// Where this wire's items keep a reasoning blob (a signature or an
267    /// encrypted copy of the reasoning), as JSON pointers tried in order.
268    /// The reply fold records the first that holds one on the block's
269    /// [`Native`](crate::message::Native). By default there are none.
270    fn blob_slots(&self) -> &'static [&'static str] {
271        &[]
272    }
273
274    /// Whether every reply on this wire states why it stopped. A reply that
275    /// then names no reason failed; a wire
276    /// with no finish vocabulary, such as a local runtime, says `false`.
277    fn states_finish_reason(&self) -> bool {
278        true
279    }
280
281    /// Whether the catalog's per-token API prices apply to this wire's
282    /// replies. A subscription backend, or a server that is usually local,
283    /// says `false`: its replies then carry only a cost they report.
284    fn catalog_priced(&self) -> bool {
285        true
286    }
287
288    /// Whether `model` binds its provider items to the request's tools and
289    /// system prompt, so a turn made under another context replays as if
290    /// from another model.
291    fn binds_context(&self, model: &str) -> bool {
292        let _ = model;
293        false
294    }
295
296    /// Whether `request` asks the provider to drop an item bound to another
297    /// context itself (Anthropic's `drop_block`), so a turn made under
298    /// another context replays verbatim rather than as another model's.
299    fn drops_unbound_items(&self, request: &crate::completion::CompletionRequest) -> bool {
300        let _ = request;
301        false
302    }
303
304    /// The target `request` is sent to, for a wire that picks its API per
305    /// request. Preparing the request and folding its reply both read the
306    /// target this names, so a turn records the API it was made on. `None`,
307    /// the default, is this target.
308    fn route(&self, request: &crate::completion::CompletionRequest) -> Option<&dyn ReplayTarget> {
309        let _ = request;
310        None
311    }
312
313    /// Whether the encoder sends a request's documents itself. Otherwise
314    /// they join the history as text before it is adapted.
315    fn takes_documents(&self) -> bool {
316        false
317    }
318
319    /// Whether this provider item requires the item after it in its turn
320    /// (Responses reasoning): when that one is not replayed, neither is
321    /// this.
322    fn needs_next(&self, item: &serde_json::Value) -> bool {
323        let _ = item;
324        false
325    }
326
327    /// The hosted-tool pair this opaque item belongs to: whether it is the
328    /// use or the result, and the id they share. A use and its result
329    /// replay only together, in one turn or across the model's turns. A use
330    /// still running when the last turn ended (a paused turn, or one whose
331    /// client call the hosted tool made) replays alone.
332    fn hosted_pair(&self, item: &serde_json::Value) -> Option<(Pairing, String)> {
333        let _ = item;
334        None
335    }
336
337    /// Whether the wire rejects a conversation whose first message after
338    /// the system prompt is not a user message. [`adapt`] then drops the
339    /// assistant turns before the first user message, with their results.
340    fn starts_with_user(&self) -> bool {
341        false
342    }
343
344    /// Where `model` takes system messages that come after the
345    /// conversation begins ([`LaterSystem`]). By default where the history
346    /// has them.
347    fn later_system(&self, model: &str) -> LaterSystem {
348        let _ = model;
349        LaterSystem::InPlace
350    }
351
352    /// Whether the wire requires user and assistant messages to alternate.
353    /// [`adapt`] then joins two messages of one role that only system
354    /// messages separate, which move after them.
355    fn alternates_roles(&self) -> bool {
356        false
357    }
358
359    /// Whether a hosted tool's use and result need the request to declare
360    /// tools. A request that declares none then sends neither.
361    fn hosted_needs_tools(&self) -> bool {
362        false
363    }
364
365    /// Whether `model` reads a tool result as several parts. When it reads
366    /// neither parts nor result images, [`adapt`] joins a result's text
367    /// parts into one.
368    fn result_parts(&self, model: &str) -> bool {
369        let _ = model;
370        false
371    }
372
373    /// Whether `block` on its own makes an assistant message this wire
374    /// sends. A turn with no such block carries nothing, so [`adapt`] drops
375    /// it and the user messages around it become one. By default every
376    /// block does.
377    fn sends_alone(&self, block: &AssistantContent) -> bool {
378        let _ = block;
379        true
380    }
381}
382
383/// Where a target takes the system messages that come after the
384/// conversation begins ([`ReplayTarget::later_system`]).
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum LaterSystem {
387    /// Where the history has them.
388    InPlace,
389    /// Joined with the leading ones into one system message.
390    Leading,
391    /// As user text where the history has them, so adding one never moves
392    /// the prefix before it.
393    UserText,
394}
395
396/// Which side of a hosted-tool pair an opaque item is.
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
398pub enum Pairing {
399    /// The hosted tool's use.
400    Use,
401    /// Its result.
402    Result,
403}
404
405/// What an encoder sends for one assistant block.
406#[derive(Debug, Clone, PartialEq)]
407pub enum Replay<'a> {
408    /// The provider item, still current, with a call's id slot spelled.
409    Item(std::borrow::Cow<'a, serde_json::Value>),
410    /// The block was edited: rebuild it from its canonical fields, keeping
411    /// these keys of its stale item.
412    Identity(serde_json::Map<String, serde_json::Value>),
413    /// Rebuild it from its canonical fields.
414    Rebuild,
415}
416
417impl AssistantContent {
418    /// What `target`'s encoder sends for this block, with call ids spelled
419    /// by `ids`. Encoders read provider items only through this.
420    pub fn replay(
421        &self,
422        target: &dyn ReplayTarget,
423        ids: &crate::providers::internal::wire_ids::WireIds,
424    ) -> Replay<'_> {
425        if let Some(item) = self.native_item() {
426            return match (self, target.call_id_slot()) {
427                (AssistantContent::ToolCall(call), Some(slot)) => {
428                    let mut item = item.clone();
429                    if let Some(id) = ids.of(&call.id) {
430                        set_pointer(&mut item, slot, serde_json::Value::String(id.to_owned()));
431                    }
432                    Replay::Item(std::borrow::Cow::Owned(item))
433                }
434                _ => Replay::Item(std::borrow::Cow::Borrowed(item)),
435            };
436        }
437        let identity = self
438            .stale_item()
439            .map(|item| target.identity(item))
440            .unwrap_or_default();
441        if identity.is_empty() {
442            Replay::Rebuild
443        } else {
444            Replay::Identity(identity)
445        }
446    }
447}
448
449/// Set the value at `pointer` in `item`, creating objects on the way.
450fn set_pointer(item: &mut serde_json::Value, pointer: &str, value: serde_json::Value) {
451    let mut at = item;
452    let mut keys = pointer.split('/').skip(1).peekable();
453    while let Some(key) = keys.next() {
454        if !at.is_object() {
455            *at = serde_json::Value::Object(serde_json::Map::new());
456        }
457        let serde_json::Value::Object(fields) = at else {
458            return;
459        };
460        if keys.peek().is_none() {
461            fields.insert(key.to_owned(), value);
462            return;
463        }
464        at = fields
465            .entry(key.to_owned())
466            .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
467    }
468}
469
470/// `history` shaped for `target`.
471///
472/// - Blank system messages are dropped.
473/// - A turn whose origin names `target`'s API, provider and model keeps its
474///   provider items. Its reasoning and blank text are dropped unless they
475///   still hold a current provider item.
476/// - Any other turn keeps only canonical fields: provider items are cleared,
477///   reasoning becomes plain text, or stays reasoning when the request asks
478///   for it and the model reads it ([`Accepts::foreign_reasoning`]);
479///   redacted or empty reasoning is dropped;
480///   [`Opaque`](crate::message::Opaque) items are dropped and call ids go
481///   through [`ReplayTarget::normalize_tool_call_id`], made distinct when two
482///   calls would share one.
483/// - Content the model does not read ([`ReplayTarget::accepts`]) is
484///   downgraded: images become placeholder text, a tool-result image moves
485///   to a user message after the results when only user images are read,
486///   and without tools calls and results become text.
487/// - Opaque items marked not to replay are always dropped, and so is a turn
488///   that ended in an error, with the results answering it. An aborted turn
489///   is read as the text it had sent, as a turn from no known model: its
490///   reasoning, its calls and the results answering them are dropped.
491/// - Calls and results pair by [`crate::conversation::pairing::pair`]: an
492///   unanswered call gets a
493///   [`NO_RESULT_PROVIDED`](crate::conversation::pairing::NO_RESULT_PROVIDED)
494///   result and an orphan result is dropped.
495/// - Adjacent user messages become one. A turn left empty is dropped, and so
496///   is a user message left empty. A message that was empty to begin with is
497///   kept, for the request boundary to reject.
498pub fn adapt(history: &[Message], target: &dyn ReplayTarget) -> Vec<Message> {
499    let history = history.iter().cloned().map(Arc::new).collect();
500    let adapted = adapt_for(history, target, &Request::default(), &mut Memo::default());
501    adapted.into_iter().map(Arc::unwrap_or_clone).collect()
502}
503
504/// What [`adapt_for`] keeps of a settled history for the next request
505/// adapted the same way: the history up to its last turn, what the messages
506/// before that turn became, and the call ids they claimed. A message's
507/// shape depends only on the messages before it, but for the last turn's,
508/// so a history that starts with the same messages (the same `Arc`s, or
509/// equal ones) is adapted from that turn on.
510#[derive(Debug, Default)]
511pub(crate) struct Memo {
512    shape: Option<Shape>,
513    given: Vec<Arc<Message>>,
514    shaped: Vec<Option<Arc<Message>>>,
515    taken: HashSet<String>,
516}
517
518/// What adapting reads besides the messages.
519#[derive(Debug, PartialEq)]
520struct Shape {
521    api: Api,
522    provider: String,
523    model: String,
524    context: Option<crate::message::Fingerprint>,
525    accepts: Accepts,
526    from_user: bool,
527}
528
529/// What `adapt` reads of the request a history is sent with.
530#[derive(Debug, Clone, Copy)]
531pub(crate) struct Request<'a> {
532    /// The model it names in place of the wire's own.
533    pub(crate) model: Option<&'a str>,
534    /// Whether it continues a conversation the provider stores.
535    pub(crate) stored: bool,
536    /// Whether it carries tools ([`ReplayTarget::declares_tools`]): a
537    /// request with none gets calls and results as text.
538    pub(crate) tools: bool,
539    /// The fingerprint of its tools and system prompt ([`context_of`]).
540    pub(crate) context: Option<crate::message::Fingerprint>,
541    /// Whether it asks for other models' reasoning as reasoning
542    /// ([`CompletionRequest::foreign_reasoning`]).
543    ///
544    /// [`CompletionRequest::foreign_reasoning`]: field@crate::completion::CompletionRequest::foreign_reasoning
545    pub(crate) foreign_reasoning: bool,
546}
547
548impl Default for Request<'_> {
549    fn default() -> Self {
550        Self {
551            model: None,
552            stored: false,
553            tools: true,
554            context: None,
555            foreign_reasoning: false,
556        }
557    }
558}
559
560/// The fingerprint of `request`'s tool definitions, by name, and of its
561/// system prompt as [`adapt`] sends it to `model` on `target`: what a
562/// context-bound item was made under. The prompt is the leading non-blank
563/// system messages, or every one when the target folds them into one, so
564/// the request before `adapt` and after it give the same fingerprint.
565pub(crate) fn context_of(
566    request: &crate::completion::CompletionRequest,
567    target: &dyn ReplayTarget,
568    model: &str,
569) -> crate::message::Fingerprint {
570    let mut tools: Vec<_> = request.tools.iter().collect();
571    tools.sort_by(|left, right| left.name.cmp(&right.name));
572    let raw = raw_tools(request);
573    let folds = target.later_system(model) == LaterSystem::Leading;
574    let mut system: Vec<&str> = request
575        .chat_history
576        .iter()
577        .map_while(|message| match &**message {
578            Message::System { content } => Some(Some(content.as_str())),
579            Message::User { .. } | Message::Assistant(_) => folds.then_some(None),
580        })
581        .flatten()
582        .filter(|content| !content.trim().is_empty())
583        .collect();
584    let joined;
585    if folds && system.len() > 1 {
586        joined = system.join("\n\n");
587        system = vec![joined.as_str()];
588    }
589    let mut fields = vec![serde_json::json!("context"), serde_json::json!(tools)];
590    fields.push(serde_json::json!(system));
591    if !raw.is_empty() {
592        fields.push(serde_json::json!(raw));
593    }
594    // Hashed as built: `Fingerprint::of` would copy it first.
595    let fields = crate::message::Canonical(&serde_json::Value::Array(fields));
596    crate::message::Fingerprint::of_canonical(&fields)
597}
598
599/// Whether `request` declares tools in `tools` or in the `tools` of its
600/// `additional_params`.
601pub(crate) fn declares_tools(request: &crate::completion::CompletionRequest) -> bool {
602    !request.tools.is_empty() || !raw_tools(request).is_empty()
603}
604
605/// The tools `request` passes as is in the `tools` of its
606/// `additional_params`, such as a provider's hosted tools.
607pub(crate) fn raw_tools(request: &crate::completion::CompletionRequest) -> &[serde_json::Value] {
608    use crate::json_utils::Lenient;
609    request
610        .additional_params
611        .as_ref()
612        .map_or(&[][..], |params| params.arr("tools"))
613}
614
615/// [`adapt`] for `request`.
616pub(crate) fn adapt_for(
617    history: Vec<Arc<Message>>,
618    target: &dyn ReplayTarget,
619    request: &Request<'_>,
620    memo: &mut Memo,
621) -> Vec<Arc<Message>> {
622    let model = request.model.unwrap_or(target.model());
623    let stored = request.stored;
624    let same = Same {
625        api: target.api(),
626        provider: target.provider(),
627        model,
628        context: request.context.filter(|_| target.binds_context(model)),
629    };
630    let mut accepts = target.accepts(model);
631    accepts.tools &= request.tools;
632    accepts.foreign_reasoning &= request.foreign_reasoning;
633    let starts_with_user = target.starts_with_user() && !stored;
634    let shape = Shape {
635        api: same.api.clone(),
636        provider: same.provider.to_owned(),
637        model: model.to_owned(),
638        context: same.context,
639        accepts,
640        from_user: starts_with_user,
641    };
642    let resumed = memo.shape.as_ref() == Some(&shape)
643        && history.get(..memo.given.len()).is_some_and(|given| {
644            given
645                .iter()
646                .zip(&memo.given)
647                .all(|(message, was)| Arc::ptr_eq(message, was) || message == was)
648        });
649    if !resumed {
650        *memo = Memo::default();
651    }
652    // The last message kept was the last turn, so it is adapted again.
653    let from = memo.given.len().saturating_sub(1);
654    let last_turn = history
655        .iter()
656        .rposition(|message| matches!(&**message, Message::Assistant(_)));
657    let (hosted, open) = hosted_pairs(&history, from, last_turn, target, &same);
658    let mut ids = Renamed {
659        to: Vec::new(),
660        before: &memo.taken,
661        taken: HashSet::new(),
662    };
663    let mut claimed = Vec::new();
664    let mut kept = 0;
665    let form = Form {
666        target,
667        model,
668        accepts,
669    };
670    // A message adapting leaves as it is goes on as the same `Arc`.
671    let mut shaped: Vec<Option<Arc<Message>>> = Vec::with_capacity(history.len());
672    shaped.extend(memo.shaped.iter().cloned());
673    for (at, message) in history.iter().enumerate().skip(from) {
674        if Some(at) == last_turn {
675            claimed = ids.taken.iter().map(|id| id.to_string()).collect();
676            kept = shaped.len();
677        }
678        match &**message {
679            Message::System { content } => {
680                if !content.trim().is_empty() {
681                    shaped.push(Some(message.clone()));
682                }
683            }
684            Message::User { content } => {
685                shaped.extend(
686                    user(message, content, &mut ids, &form)
687                        .into_iter()
688                        .map(Some),
689                );
690            }
691            Message::Assistant(turn) if matches!(turn.stop, Some(StopReason::Aborted(_))) => {
692                shaped.push(stopped(turn));
693            }
694            // A failed turn is skipped, with the results answering it.
695            Message::Assistant(turn)
696                if turn.stop.as_ref().is_some_and(|stop| stop.is_failure()) =>
697            {
698                shaped.push(None);
699            }
700            Message::Assistant(turn) => {
701                // A use can still be running only in the last turn, and only
702                // while nothing new follows it or the turn awaits a client call.
703                let last = Some(at) == last_turn
704                    && (turn
705                        .content
706                        .iter()
707                        .any(|block| matches!(block, AssistantContent::ToolCall(_)))
708                        || history
709                            .get(at + 1..)
710                            .into_iter()
711                            .flatten()
712                            .all(|message| matches!(&**message, Message::System { .. })));
713                let here: HashSet<usize> = hosted
714                    .iter()
715                    .filter(|(message, _)| *message == at)
716                    .map(|(_, block)| *block)
717                    .collect();
718                shaped.push(assistant(
719                    message, turn, &form, &same, &mut ids, &here, last,
720                ));
721            }
722        }
723    }
724    let (before, tail) = shaped.split_at(memo.shaped.len().min(shaped.len()));
725    let last = before.last().and_then(Option::as_deref);
726    if settled(tail, last, starts_with_user, accepts.tools) {
727        drop(ids);
728        // The next history adapts from the last turn on unless a hosted use
729        // before it may yet pair.
730        match last_turn.filter(|_| !open) {
731            Some(upto) => {
732                let given = history.get(memo.given.len()..=upto).unwrap_or_default();
733                memo.given.extend(given.iter().cloned());
734                let shaped = shaped.get(memo.shaped.len()..kept).unwrap_or_default();
735                memo.shaped.extend(shaped.iter().cloned());
736                memo.taken.extend(claimed);
737                memo.shape = Some(shape);
738            }
739            None => *memo = Memo::default(),
740        }
741        let settled: Vec<Arc<Message>> = shaped.into_iter().flatten().collect();
742        #[cfg(test)]
743        {
744            let shaped = settled.iter().map(|message| Some(Message::clone(message)));
745            let rebuilt = reshaped(shaped.collect(), target, model, stored, accepts.tools);
746            let settled: Vec<&Message> = settled.iter().map(Arc::as_ref).collect();
747            assert_eq!(
748                settled,
749                rebuilt.iter().collect::<Vec<_>>(),
750                "a settled history changed"
751            );
752        }
753        return settled;
754    }
755    drop(ids);
756    *memo = Memo::default();
757    let shaped = shaped
758        .into_iter()
759        .map(|message| message.map(Arc::unwrap_or_clone));
760    let shaped = reshaped(shaped.collect(), target, model, stored, accepts.tools);
761    shaped.into_iter().map(Arc::new).collect()
762}
763
764/// Whether `history`, shaped message by message, is already what pairing
765/// and the role rules make of it, so they can be skipped: no turn was
766/// dropped, the system messages all lead, user and assistant messages
767/// alternate (from a user message when `starts_with_user`), and each turn's
768/// calls are answered exactly, results first, by the user message after it.
769/// Without `tools`, it holds no call or result at all.
770fn settled<'a>(
771    history: &'a [Option<Arc<Message>>],
772    mut last: Option<&'a Message>,
773    starts_with_user: bool,
774    tools: bool,
775) -> bool {
776    let mut waiting: Vec<&CallId> = Vec::new();
777    for message in history {
778        let Some(message) = message.as_deref() else {
779            return false;
780        };
781        match (message, last) {
782            (Message::System { .. }, None) => continue,
783            (Message::System { .. }, Some(_))
784            | (Message::User { .. }, Some(Message::User { .. }))
785            | (Message::Assistant(_), Some(Message::Assistant(_))) => return false,
786            (Message::Assistant(_), None) if starts_with_user => return false,
787            // A turn follows nothing or a user message, which answered
788            // every call before it, so nothing waits here.
789            (Message::Assistant(turn), _) => {
790                waiting.extend(turn.tool_calls().map(|call| &call.id));
791                if !tools && !waiting.is_empty() {
792                    return false;
793                }
794            }
795            (Message::User { content }, _) => {
796                let results = content.iter().take_while(|part| is_result(part)).count();
797                let answered = content.iter().take(results).all(|part| match part {
798                    UserContent::ToolResult(result) => {
799                        let at = waiting.iter().position(|id| **id == result.call);
800                        at.map(|at| waiting.remove(at)).is_some()
801                    }
802                    _ => false,
803                });
804                if content.is_empty()
805                    || (!tools && results > 0)
806                    || !answered
807                    || !waiting.is_empty()
808                    || content.iter().skip(results).any(is_result)
809                {
810                    return false;
811                }
812            }
813        }
814        last = Some(message);
815    }
816    waiting.is_empty()
817}
818
819/// `shaped`, adapted message by message, paired and put in the roles
820/// `target` takes.
821fn reshaped(
822    shaped: Vec<Option<Message>>,
823    target: &dyn ReplayTarget,
824    model: &str,
825    stored: bool,
826    tools: bool,
827) -> Vec<Message> {
828    // Calls and results pair first, so an orphan result goes whether or not
829    // the request declares tools; only then does a request without tools
830    // get them as text, with no result made up for an unanswered call.
831    let pairing = crate::conversation::pairing::Pairing {
832        stored,
833        answers: tools,
834    };
835    let shaped = merge_users(crate::conversation::pairing::pair(shaped, pairing).messages);
836    let shaped = if tools { shaped } else { tools_as_text(shaped) };
837    let shaped = match target.later_system(model) {
838        LaterSystem::InPlace => shaped,
839        LaterSystem::Leading => leading_system(shaped),
840        LaterSystem::UserText => system_as_user_text(shaped),
841    };
842    let shaped = if target.starts_with_user() && !stored {
843        from_first_user(shaped)
844    } else {
845        shaped
846    };
847    if target.alternates_roles() {
848        alternated(shaped)
849    } else {
850        shaped
851    }
852}
853
854/// `points`, which name `written` (a request's history before its
855/// `documents` join it), as they fall in `adapted`, what [`adapt_for`] made
856/// of it with `request`. A message point becomes the longest part of
857/// `adapted` that the adaption of its prefix alone begins with: the point
858/// never covers what follows the prefix, however the adaption dropped,
859/// added or joined messages around it. A point past the end of `written`
860/// goes.
861pub(crate) fn cache_points(
862    points: &[crate::completion::CachePoint],
863    written: &[Arc<Message>],
864    documents: Option<&Message>,
865    adapted: &[Arc<Message>],
866    target: &dyn ReplayTarget,
867    request: &Request<'_>,
868) -> Vec<crate::completion::CachePoint> {
869    use crate::completion::CachePoint;
870    let prefix_of = |turns: usize, items: Option<usize>| -> Option<Vec<Arc<Message>>> {
871        let mut prefix = Vec::new();
872        let mut counted = 0;
873        for message in written {
874            if counted == turns {
875                break;
876            }
877            let mut message = Arc::clone(message);
878            if !matches!(*message, Message::System { .. }) {
879                counted += 1;
880            }
881            // Only the message the point cuts inside is copied.
882            if let Some(items) = items.filter(|_| counted == turns) {
883                match Arc::make_mut(&mut message) {
884                    Message::User { content } => content.truncate(items),
885                    Message::Assistant(turn) => turn.content.truncate(items),
886                    Message::System { .. } => {}
887                }
888            }
889            prefix.push(message);
890        }
891        (counted == turns).then_some(prefix)
892    };
893    points
894        .iter()
895        .filter_map(|point| {
896            let prefix = match *point {
897                CachePoint::Tools | CachePoint::System | CachePoint::Messages(0) => {
898                    return Some(*point);
899                }
900                CachePoint::Messages(turns) => prefix_of(turns, None),
901                CachePoint::Within { message, items } => message
902                    .checked_add(1)
903                    .and_then(|turns| prefix_of(turns, Some(items))),
904            };
905            let mut prefix = prefix?;
906            if let Some(documents) = documents {
907                crate::completion::request::insert_after_leading_system(
908                    &mut prefix,
909                    documents.clone(),
910                );
911            }
912            let prefix = adapt_for(prefix, target, request, &mut Memo::default());
913            Some(shared_prefix(adapted, &prefix))
914        })
915        .collect()
916}
917
918/// Where `prefix` stops being the start of `history`, both adapted, as a
919/// cache point over `history`. System messages are not counted.
920fn shared_prefix(
921    history: &[Arc<Message>],
922    prefix: &[Arc<Message>],
923) -> crate::completion::CachePoint {
924    use crate::completion::CachePoint;
925    fn turns(history: &[Arc<Message>]) -> impl Iterator<Item = &Message> {
926        history
927            .iter()
928            .map(Arc::as_ref)
929            .filter(|message| !matches!(message, Message::System { .. }))
930    }
931    fn lead<T: PartialEq>(whole: &[T], part: &[T]) -> (usize, usize, usize) {
932        let shared = whole
933            .iter()
934            .zip(part)
935            .take_while(|(whole, part)| whole == part)
936            .count();
937        (shared, whole.len(), part.len())
938    }
939    let mut message = 0;
940    for (whole, part) in turns(history).zip(turns(prefix)) {
941        let (shared, whole, part) = match (whole, part) {
942            (Message::User { content: whole }, Message::User { content: part }) => {
943                lead(whole, part)
944            }
945            (Message::Assistant(whole), Message::Assistant(part)) => {
946                lead(&whole.content, &part.content)
947            }
948            _ => (0, 1, 1),
949        };
950        if shared == whole && shared == part {
951            message += 1;
952            continue;
953        }
954        return if shared == 0 {
955            CachePoint::Messages(message)
956        } else {
957            CachePoint::Within {
958                message,
959                items: shared,
960            }
961        };
962    }
963    CachePoint::Messages(message)
964}
965
966/// `history` with its calls and results as text, for a request that
967/// declares no tools. Keys are sorted, so the text is the same however the
968/// provider ordered the arguments.
969fn tools_as_text(history: Vec<Message>) -> Vec<Message> {
970    let history = history
971        .into_iter()
972        .map(|message| match message {
973            Message::Assistant(mut turn) => {
974                for block in &mut turn.content {
975                    if let AssistantContent::ToolCall(call) = block {
976                        *block = AssistantContent::Text(Text::new(format!(
977                            "[called tool {} with {}]",
978                            call.function.name,
979                            crate::json_utils::to_canonical_string(
980                                &call.function.arguments_value()
981                            )
982                        )));
983                    }
984                }
985                Message::Assistant(turn)
986            }
987            Message::User { content } => Message::User {
988                content: content
989                    .into_iter()
990                    .map(|part| match part {
991                        UserContent::ToolResult(result) => UserContent::text(result_text(&result)),
992                        part => part,
993                    })
994                    .collect(),
995            },
996            message => message,
997        })
998        .collect();
999    merge_users(history)
1000}
1001
1002/// `history` from its first user message, after the leading system
1003/// messages: an assistant turn before it goes, with the results answering
1004/// it, until a user message leads.
1005fn from_first_user(mut history: Vec<Message>) -> Vec<Message> {
1006    let lead = history
1007        .iter()
1008        .take_while(|message| matches!(message, Message::System { .. }))
1009        .count();
1010    while let Some(Message::Assistant(turn)) = history.get(lead) {
1011        let calls: HashSet<CallId> = turn.tool_calls().map(|call| call.id.clone()).collect();
1012        history.remove(lead);
1013        if let Some(Message::User { content }) = history.get_mut(lead) {
1014            content.retain(|part| {
1015                !matches!(part, UserContent::ToolResult(result) if calls.contains(&result.call))
1016            });
1017            if content.is_empty() {
1018                history.remove(lead);
1019            }
1020        }
1021    }
1022    history
1023}
1024
1025/// Whether `history` has a system message after its first user or
1026/// assistant message.
1027fn has_later_system(history: &[Message]) -> bool {
1028    history
1029        .iter()
1030        .skip_while(|message| matches!(message, Message::System { .. }))
1031        .any(|message| matches!(message, Message::System { .. }))
1032}
1033
1034/// `history` with every later system message as user text where it stands.
1035fn system_as_user_text(history: Vec<Message>) -> Vec<Message> {
1036    let mut leading = true;
1037    let history = history
1038        .into_iter()
1039        .map(|message| match message {
1040            Message::System { content } if !leading => Message::User {
1041                content: vec![UserContent::text(content)],
1042            },
1043            message => {
1044                leading &= matches!(message, Message::System { .. });
1045                message
1046            }
1047        })
1048        .collect();
1049    merge_users(history)
1050}
1051
1052/// `history` with no two user or assistant messages of one role in a row:
1053/// two that only system messages separate become one, and the system
1054/// messages move after them.
1055fn alternated(history: Vec<Message>) -> Vec<Message> {
1056    let mut alternated: Vec<Message> = Vec::with_capacity(history.len());
1057    let mut held: Vec<Message> = Vec::new();
1058    for message in history {
1059        let started = alternated
1060            .iter()
1061            .any(|message| !matches!(message, Message::System { .. }));
1062        match (message, alternated.last_mut()) {
1063            (message @ Message::System { .. }, _) if started => held.push(message),
1064            (Message::User { content }, Some(Message::User { content: previous })) => {
1065                previous.extend(content);
1066            }
1067            (Message::Assistant(turn), Some(Message::Assistant(previous))) => {
1068                if previous.origin != turn.origin {
1069                    previous.origin = None;
1070                }
1071                previous.stop = turn.stop;
1072                previous.content.extend(turn.content);
1073            }
1074            (message, _) => {
1075                alternated.append(&mut held);
1076                alternated.push(message);
1077            }
1078        }
1079    }
1080    alternated.append(&mut held);
1081    alternated
1082}
1083
1084/// `history` with its system messages joined into one leading message, when
1085/// any comes after the conversation begins.
1086fn leading_system(history: Vec<Message>) -> Vec<Message> {
1087    if !has_later_system(&history) {
1088        return history;
1089    }
1090    let (system, rest): (Vec<Message>, Vec<Message>) = history
1091        .into_iter()
1092        .partition(|message| matches!(message, Message::System { .. }));
1093    let prompt: Vec<String> = system
1094        .into_iter()
1095        .filter_map(|message| match message {
1096            Message::System { content } => Some(content),
1097            Message::User { .. } | Message::Assistant(_) => None,
1098        })
1099        .collect();
1100    let mut history = Vec::with_capacity(rest.len() + 1);
1101    if !prompt.is_empty() {
1102        history.push(Message::system(prompt.join("\n\n")));
1103    }
1104    // Moving a system message out may leave two user messages adjacent.
1105    history.extend(merge_users(rest));
1106    history
1107}
1108
1109/// Call ids as the target sends them. Every provider id in the adapted
1110/// history is distinct: a repeated one, in one turn or across turns, takes a
1111/// counter. The renames of the latest turn map its results, in call order,
1112/// so two calls that shared an id are answered by the results that followed
1113/// them in turn.
1114struct Renamed<'a> {
1115    /// For the latest turn: each call's source id and the id it took, in
1116    /// call order.
1117    to: Vec<(Cow<'a, CallId>, Cow<'a, CallId>)>,
1118    /// The ids an earlier adaption of the same messages claimed ([`Memo`]).
1119    before: &'a HashSet<String>,
1120    taken: HashSet<Cow<'a, str>>,
1121}
1122
1123impl<'a> Renamed<'a> {
1124    /// A new turn begins: its results answer only its own calls.
1125    fn turn(&mut self) {
1126        self.to.clear();
1127    }
1128
1129    /// The id the call `source` takes, wanting `wanted`: `wanted`, or, when
1130    /// an earlier call took it, the same id with its tail replaced by a
1131    /// counter until it is free. A counter of lowercase alphanumerics keeps
1132    /// the id's length and is legal on every wire. An id rig issued stays
1133    /// as it is: [`WireIds`] spells it.
1134    ///
1135    /// [`WireIds`]: crate::providers::internal::wire_ids::WireIds
1136    fn claim(&mut self, source: Cow<'a, CallId>, wanted: Cow<'a, str>) -> Cow<'a, CallId> {
1137        let id = match &*source {
1138            CallId::Local(_) => source.clone(),
1139            CallId::Provider(_) => {
1140                let mut id = wanted.clone();
1141                let mut attempt: u64 = 1;
1142                while self.before.contains(id.as_ref()) || self.taken.contains(&id) {
1143                    id = Cow::Owned(with_counter(&wanted, attempt));
1144                    attempt += 1;
1145                }
1146                self.taken.insert(id.clone());
1147                if id == source.wire() {
1148                    source.clone()
1149                } else {
1150                    Cow::Owned(CallId::from_wire(id))
1151                }
1152            }
1153        };
1154        self.to.push((source, id.clone()));
1155        id
1156    }
1157
1158    /// The id a result for `source` answers: the next call of the latest
1159    /// turn that had it, the last one once each was answered.
1160    fn answer(&mut self, source: &CallId) -> Option<Cow<'a, CallId>> {
1161        let mut calls = (self.to.iter().enumerate())
1162            .filter(|(_, (from, _))| from.as_ref() == source)
1163            .map(|(at, _)| at);
1164        let first = calls.next()?;
1165        if calls.next().is_some() {
1166            Some(self.to.remove(first).1)
1167        } else {
1168            self.to.get(first).map(|(_, id)| id.clone())
1169        }
1170    }
1171}
1172
1173/// `id` with its last characters replaced by `attempt` in base 36.
1174fn with_counter(id: &str, attempt: u64) -> String {
1175    let mut digits = Vec::new();
1176    let mut value = attempt;
1177    while value > 0 {
1178        digits.push(char::from_digit((value % 36) as u32, 36).unwrap_or('0'));
1179        value /= 36;
1180    }
1181    digits.reverse();
1182    let keep = id.chars().count().saturating_sub(digits.len());
1183    id.chars().take(keep).chain(digits).collect()
1184}
1185
1186/// The model a history is sent to, as a turn's origin is compared with it.
1187struct Same<'a> {
1188    api: Api,
1189    provider: &'a str,
1190    model: &'a str,
1191    /// The request's context, when the target binds items to it.
1192    context: Option<crate::message::Fingerprint>,
1193}
1194
1195impl Same<'_> {
1196    /// Whether `origin` is this model, made under this context where the
1197    /// target binds items to it.
1198    fn is(&self, origin: &Origin) -> bool {
1199        origin.same_model(&self.api, self.provider, self.model)
1200            && self
1201                .context
1202                .is_none_or(|context| origin.context == Some(context))
1203    }
1204}
1205
1206/// The turn `message`, holding `turn`, shaped for `form` and the target
1207/// named by `same`: `message` itself when nothing in it changes, nothing
1208/// when no block it sends alone is left.
1209fn assistant<'a>(
1210    message: &Arc<Message>,
1211    turn: &'a AssistantMessage,
1212    form: &Form<'_>,
1213    same_model: &Same<'_>,
1214    ids: &mut Renamed<'a>,
1215    hosted: &HashSet<usize>,
1216    last: bool,
1217) -> Option<Arc<Message>> {
1218    let Form {
1219        target,
1220        model,
1221        accepts,
1222    } = *form;
1223    let same = turn
1224        .origin
1225        .as_ref()
1226        .is_some_and(|origin| same_model.is(origin));
1227    ids.turn();
1228    let content: Vec<Option<Cow<'a, AssistantContent>>> = turn
1229        .content
1230        .iter()
1231        .map(|block| {
1232            let block = if same {
1233                match block {
1234                    AssistantContent::ToolCall(call) => {
1235                        let id = ids.claim(Cow::Borrowed(&call.id), call.id.wire());
1236                        if *id == call.id {
1237                            Cow::Borrowed(block)
1238                        } else {
1239                            // A rename is rig's, not an edit: replay spells
1240                            // the new id into the item's slot, so the item
1241                            // stays.
1242                            let keep =
1243                                block.native_item().is_some() && target.call_id_slot().is_some();
1244                            let id = id.into_owned();
1245                            let renamed = crate::message::ToolCall { id, ..call.clone() };
1246                            let mut renamed = AssistantContent::ToolCall(renamed);
1247                            if keep
1248                                && let Some(native) = renamed.native_slot().and_then(Option::take)
1249                            {
1250                                let blob_slot = native.blob_slot;
1251                                renamed = renamed.with_native(native.item);
1252                                if let Some(Some(native)) = renamed.native_slot() {
1253                                    native.blob_slot = blob_slot;
1254                                }
1255                            } else if let Some(Some(native)) = renamed.native_slot() {
1256                                native.edited = true;
1257                            }
1258                            Cow::Owned(renamed)
1259                        }
1260                    }
1261                    // The capability holds for the model's own turns too: an
1262                    // image it made but does not read back is left out.
1263                    AssistantContent::Image(_) if !accepts.assistant_images => return None,
1264                    block => Cow::Borrowed(block),
1265                }
1266            } else {
1267                Cow::Owned(match block.canonical() {
1268                    AssistantContent::Reasoning(reasoning) => {
1269                        if reasoning.redacted || reasoning.text.trim().is_empty() {
1270                            return None;
1271                        }
1272                        if accepts.foreign_reasoning {
1273                            AssistantContent::Reasoning(reasoning)
1274                        } else {
1275                            AssistantContent::Text(Text::new(reasoning.text))
1276                        }
1277                    }
1278                    AssistantContent::Opaque(_) => return None,
1279                    AssistantContent::Image(_) if !accepts.assistant_images => {
1280                        AssistantContent::Text(Text::new(IMAGE_OMITTED))
1281                    }
1282                    AssistantContent::ToolCall(mut call) => {
1283                        let normalized = target.normalize_tool_call_id(
1284                            &call.id.wire(),
1285                            model,
1286                            turn.origin.as_ref(),
1287                        );
1288                        let source = Cow::Owned(call.id);
1289                        call.id = ids.claim(source, Cow::Owned(normalized)).into_owned();
1290                        AssistantContent::ToolCall(call)
1291                    }
1292                    block => block,
1293                })
1294            };
1295            // Keys sorted, so the text is the same however the provider
1296            // ordered the arguments.
1297            let block = match &*block {
1298                AssistantContent::Opaque(opaque)
1299                    if !accepts.tools
1300                        && target.hosted_needs_tools()
1301                        && target.hosted_pair(&opaque.item).is_some() =>
1302                {
1303                    return None;
1304                }
1305                _ => block,
1306            };
1307            (!block.is_blank()).then_some(block)
1308        })
1309        .collect();
1310    let content: Vec<Cow<'a, AssistantContent>> = if same {
1311        paired(content, target, accepts.tools, hosted, last)
1312    } else {
1313        content.into_iter().flatten().collect()
1314    };
1315    let content: Vec<Cow<'a, AssistantContent>> = content
1316        .into_iter()
1317        .map(|block| match &*block {
1318            AssistantContent::Image(image) if image.native.is_none() => {
1319                if sendable_as_is(image) && form.sends(image, Place::Assistant) {
1320                    return block;
1321                }
1322                let image = sendable_image(image.clone());
1323                Cow::Owned(
1324                    if matches!(image.data, DocumentSourceKind::Unknown)
1325                        || !target.encodes(model, Media::Image(&image, Place::Assistant))
1326                    {
1327                        AssistantContent::Text(Text::new(IMAGE_UNSENDABLE))
1328                    } else {
1329                        AssistantContent::Image(image)
1330                    },
1331                )
1332            }
1333            _ => block,
1334        })
1335        .collect();
1336    if !turn.content.is_empty() && !content.iter().any(|block| target.sends_alone(block)) {
1337        return None;
1338    }
1339    if content.len() == turn.content.len()
1340        && content
1341            .iter()
1342            .all(|block| matches!(block, Cow::Borrowed(_)))
1343    {
1344        return Some(message.clone());
1345    }
1346    Some(Arc::new(Message::Assistant(AssistantMessage {
1347        content: content.into_iter().map(Cow::into_owned).collect(),
1348        origin: turn.origin.clone(),
1349        stop: turn.stop.clone(),
1350    })))
1351}
1352
1353/// The stopped turn `turn` as the text it had sent, shaped as a reply from
1354/// no known model: canonical, blank text dropped, nothing when no text is
1355/// left. Its reasoning and calls are dropped, so the results answering them
1356/// go too.
1357fn stopped(turn: &AssistantMessage) -> Option<Arc<Message>> {
1358    let content: Vec<AssistantContent> = turn
1359        .content
1360        .iter()
1361        .filter(|block| matches!(block, AssistantContent::Text(_)))
1362        .map(AssistantContent::canonical)
1363        .filter(|block| !block.is_blank())
1364        .collect();
1365    (!content.is_empty()).then(|| Arc::new(Message::Assistant(AssistantMessage::new(content))))
1366}
1367
1368/// The hosted uses and results that pair, as (message, block) positions in
1369/// the model's own replayed turns. A use pairs with the next result of its id,
1370/// in its turn or a later one: a programmatic tool call's code execution
1371/// returns its result in the turn after the client call it made. A second use
1372/// of an id before its result leaves the first unpaired. Read from message
1373/// `from` on, with whether a use was still open when message `boundary`
1374/// began.
1375fn hosted_pairs(
1376    history: &[Arc<Message>],
1377    from: usize,
1378    boundary: Option<usize>,
1379    target: &dyn ReplayTarget,
1380    same: &Same<'_>,
1381) -> (HashSet<(usize, usize)>, bool) {
1382    let mut open: HashMap<String, (usize, usize)> = HashMap::new();
1383    let mut paired = HashSet::new();
1384    let mut open_at_boundary = false;
1385    for (at, message) in history.iter().enumerate().skip(from) {
1386        if Some(at) == boundary {
1387            open_at_boundary = !open.is_empty();
1388        }
1389        let Message::Assistant(turn) = &**message else {
1390            continue;
1391        };
1392        if turn.stop.as_ref().is_some_and(|stop| stop.is_failure())
1393            || !turn.origin.as_ref().is_some_and(|origin| same.is(origin))
1394        {
1395            continue;
1396        }
1397        for (index, block) in turn.content.iter().enumerate() {
1398            let AssistantContent::Opaque(opaque) = block else {
1399                continue;
1400            };
1401            if !opaque.replay {
1402                continue;
1403            }
1404            match target.hosted_pair(&opaque.item) {
1405                Some((Pairing::Use, id)) => {
1406                    open.insert(id, (at, index));
1407                }
1408                Some((Pairing::Result, id)) => {
1409                    if let Some(used) = open.remove(&id) {
1410                        paired.insert(used);
1411                        paired.insert((at, index));
1412                    }
1413                }
1414                None => {}
1415            }
1416        }
1417    }
1418    (paired, open_at_boundary)
1419}
1420
1421/// A same-model turn's kept blocks (`None` where `adapt` dropped one), with
1422/// every block whose partner is gone dropped too: an item that needs the one
1423/// after it ([`ReplayTarget::needs_next`]), edited or not, when that one is
1424/// dropped or only rebuilt, and a hosted use or result whose block position
1425/// is not in `hosted` ([`ReplayTarget::hosted_pair`]). In the `last` turn,
1426/// one that ends the history or awaits a client call, a use followed only by
1427/// calls and opaque items is still running and stays.
1428fn paired<'a>(
1429    mut content: Vec<Option<Cow<'a, AssistantContent>>>,
1430    target: &dyn ReplayTarget,
1431    tools: bool,
1432    hosted: &HashSet<usize>,
1433    last: bool,
1434) -> Vec<Cow<'a, AssistantContent>> {
1435    let pair = |block: &AssistantContent| match block {
1436        AssistantContent::Opaque(opaque) if opaque.replay => target.hosted_pair(&opaque.item),
1437        _ => None,
1438    };
1439    for at in 0..content.len() {
1440        let Some((side, _)) = content.get(at).and_then(Option::as_deref).and_then(pair) else {
1441            continue;
1442        };
1443        let running = last
1444            && side == Pairing::Use
1445            && content
1446                .get(at + 1..)
1447                .into_iter()
1448                .flatten()
1449                .flatten()
1450                .all(|block| {
1451                    matches!(
1452                        **block,
1453                        AssistantContent::ToolCall(_) | AssistantContent::Opaque(_)
1454                    )
1455                });
1456        if !hosted.contains(&at)
1457            && !running
1458            && let Some(slot) = content.get_mut(at)
1459        {
1460            *slot = None;
1461        }
1462    }
1463    // The item each block goes back as, read once: its item while current,
1464    // or a rebuild that keeps the item's identity (an edited block rebuilt
1465    // under it needs its partner as much as the item itself); whether it
1466    // goes back as one, and whether that item needs the block after it.
1467    let (kept, needs): (Vec<bool>, Vec<bool>) = content
1468        .iter()
1469        .map(|block| {
1470            let item = block.as_deref().and_then(|block| match block {
1471                AssistantContent::Opaque(opaque) => opaque.replay.then_some(&opaque.item),
1472                block => block
1473                    .native()
1474                    .filter(|(item, current)| *current || !target.identity(item).is_empty())
1475                    .map(|(item, _)| item),
1476            });
1477            (
1478                item.is_some(),
1479                item.is_some_and(|item| target.needs_next(item)),
1480            )
1481        })
1482        .unzip();
1483    for at in (0..content.len()).rev() {
1484        // The partner must go back as the item the provider issued: its item,
1485        // or a rebuild that keeps its identity, never a block with neither.
1486        // A request without tools gets a call as text.
1487        let next_gone = content
1488            .get(at + 1)
1489            .and_then(Option::as_deref)
1490            .is_none_or(|next| {
1491                !kept.get(at + 1).copied().unwrap_or_default()
1492                    || (!tools && matches!(next, AssistantContent::ToolCall(_)))
1493            });
1494        if needs.get(at).copied().unwrap_or_default()
1495            && next_gone
1496            && let Some(slot) = content.get_mut(at)
1497        {
1498            *slot = None;
1499        }
1500    }
1501    content.into_iter().flatten().collect()
1502}
1503
1504/// The target a user message is shaped for.
1505struct Form<'a> {
1506    target: &'a dyn ReplayTarget,
1507    model: &'a str,
1508    accepts: Accepts,
1509}
1510
1511impl Form<'_> {
1512    /// Whether an image the model reads at `place` can be sent there.
1513    fn sends(&self, image: &crate::message::Image, place: Place) -> bool {
1514        let reads = match place {
1515            Place::User => self.accepts.user_images,
1516            Place::ToolResult => self.accepts.tool_result_images,
1517            Place::Assistant => self.accepts.assistant_images,
1518        };
1519        reads
1520            && !matches!(image.data, DocumentSourceKind::Unknown)
1521            && self.target.encodes(self.model, Media::Image(image, place))
1522    }
1523}
1524
1525/// The user message `message`, holding `content`, shaped for `form`: one
1526/// message, or two when tool-result images move to a message of their own,
1527/// or `message` itself when nothing in it changes.
1528fn user<'a>(
1529    message: &Arc<Message>,
1530    content: &'a [UserContent],
1531    ids: &mut Renamed<'a>,
1532    form: &Form<'_>,
1533) -> Vec<Arc<Message>> {
1534    let mut shaped: Vec<Cow<'_, UserContent>> = Vec::with_capacity(content.len());
1535    let mut attached = Vec::new();
1536    for part in content {
1537        let placeholder = match part {
1538            UserContent::Image(image)
1539                if sendable_as_is(image) && form.sends(image, Place::User) =>
1540            {
1541                shaped.push(Cow::Borrowed(part));
1542                continue;
1543            }
1544            UserContent::Image(image) => {
1545                let image = sendable_image(image.clone());
1546                if form.sends(&image, Place::User) {
1547                    shaped.push(Cow::Owned(UserContent::Image(image)));
1548                    continue;
1549                }
1550                if form.accepts.user_images {
1551                    IMAGE_UNSENDABLE
1552                } else {
1553                    IMAGE_OMITTED
1554                }
1555            }
1556            UserContent::Audio(audio) => {
1557                let mut audio = audio.clone();
1558                audio.data = sendable(audio.data);
1559                if !matches!(audio.data, DocumentSourceKind::Unknown)
1560                    && form.target.encodes(form.model, Media::Audio(&audio))
1561                {
1562                    shaped.push(Cow::Owned(UserContent::Audio(audio)));
1563                    continue;
1564                }
1565                AUDIO_UNSENDABLE
1566            }
1567            UserContent::Video(video) => {
1568                let mut video = video.clone();
1569                video.data = sendable(video.data);
1570                if !matches!(video.data, DocumentSourceKind::Unknown)
1571                    && form.target.encodes(form.model, Media::Video(&video))
1572                {
1573                    shaped.push(Cow::Owned(UserContent::Video(video)));
1574                    continue;
1575                }
1576                VIDEO_UNSENDABLE
1577            }
1578            UserContent::Document(document) => {
1579                let mut document = document.clone();
1580                if let DocumentData::File(source) = document.data {
1581                    document.data = DocumentData::File(sendable(source));
1582                }
1583                if document.data != DocumentData::File(DocumentSourceKind::Unknown)
1584                    && form.target.encodes(form.model, Media::Document(&document))
1585                {
1586                    shaped.push(Cow::Owned(UserContent::Document(document)));
1587                    continue;
1588                }
1589                // Base64 of a media type other than PDF that decodes as
1590                // UTF-8 is text the wire can still read.
1591                let text = match document.data {
1592                    DocumentData::Text(text) => Some(text),
1593                    DocumentData::File(DocumentSourceKind::Base64(data))
1594                        if !matches!(document.media_type, None | Some(DocumentMediaType::PDF)) =>
1595                    {
1596                        BASE64_STANDARD
1597                            .decode(data.as_bytes())
1598                            .ok()
1599                            .and_then(|bytes| String::from_utf8(bytes).ok())
1600                    }
1601                    DocumentData::File(_) => None,
1602                };
1603                if let Some(text) = text {
1604                    shaped.push(Cow::Owned(UserContent::text(text)));
1605                    continue;
1606                }
1607                DOCUMENT_UNSENDABLE
1608            }
1609            UserContent::ToolResult(result) => {
1610                let answer = ids.answer(&result.call);
1611                let parts = form.accepts.tool_result_images || form.target.result_parts(form.model);
1612                if answer.as_ref().is_none_or(|id| **id == result.call)
1613                    && result_as_is(result, form, parts)
1614                {
1615                    shaped.push(Cow::Borrowed(part));
1616                    continue;
1617                }
1618                let mut result = result.clone();
1619                if let Some(id) = answer {
1620                    result.call = id.into_owned();
1621                }
1622                result.content = result_images(result.content, form, &mut attached);
1623                result.content = result_text_parts(result.content, parts, result.is_error);
1624                shaped.push(Cow::Owned(UserContent::ToolResult(result)));
1625                continue;
1626            }
1627            // Blank text says nothing, and several providers reject it.
1628            UserContent::Text(text) if text.text.trim().is_empty() => continue,
1629            UserContent::Text(_) => {
1630                shaped.push(Cow::Borrowed(part));
1631                continue;
1632            }
1633        };
1634        let repeated = matches!(
1635            shaped.last().map(|part| &**part),
1636            Some(UserContent::Text(text)) if text.text == placeholder
1637        );
1638        if !repeated {
1639            shaped.push(Cow::Owned(UserContent::text(placeholder)));
1640        }
1641    }
1642    let unchanged =
1643        shaped.len() == content.len() && shaped.iter().all(|part| matches!(part, Cow::Borrowed(_)));
1644    if !shaped.is_empty() && unchanged && attached.is_empty() {
1645        return vec![message.clone()];
1646    }
1647    let mut messages = Vec::new();
1648    if !shaped.is_empty() {
1649        let content = shaped.into_iter().map(Cow::into_owned).collect();
1650        messages.push(Arc::new(Message::User { content }));
1651    }
1652    if !attached.is_empty() {
1653        let mut content = vec![UserContent::text(TOOL_IMAGES_HEADING)];
1654        content.extend(attached.into_iter().map(UserContent::Image));
1655        messages.push(Arc::new(Message::User { content }));
1656    }
1657    messages
1658}
1659
1660/// Whether `result` is sent as it is: every image in it is sent in the
1661/// result as it is ([`result_images`]), and its text needs no placeholder
1662/// or joining ([`result_text_parts`]).
1663fn result_as_is(result: &ToolResult, form: &Form<'_>, parts: bool) -> bool {
1664    let images = result.content.iter().all(|part| match part {
1665        ToolResultContent::Image(image) => {
1666            sendable_as_is(image) && form.sends(image, Place::ToolResult)
1667        }
1668        ToolResultContent::Text(_) | ToolResultContent::Json { .. } => true,
1669    });
1670    images && !says_nothing(&result.content) && (parts || texts(&result.content) < 2)
1671}
1672
1673/// What replaces an empty tool result: models answer a call whose result
1674/// says nothing better than one with no content.
1675pub const NO_TOOL_OUTPUT: &str = "(no tool output)";
1676
1677/// `content` of a result that has nothing to say, said plainly, and joined
1678/// into one text unless the model reads several `parts`: Gemini 2 rejects a
1679/// result of several parts.
1680fn result_text_parts(
1681    content: Vec<ToolResultContent>,
1682    parts: bool,
1683    is_error: bool,
1684) -> Vec<ToolResultContent> {
1685    if says_nothing(&content) {
1686        let text = if is_error {
1687            format!("[tool error] {NO_TOOL_OUTPUT}")
1688        } else {
1689            NO_TOOL_OUTPUT.to_owned()
1690        };
1691        return vec![ToolResultContent::text(text)];
1692    }
1693    if parts || texts(&content) < 2 {
1694        return content;
1695    }
1696    let mut joined: Vec<String> = Vec::new();
1697    let mut images = Vec::new();
1698    for part in content {
1699        match part {
1700            ToolResultContent::Text(text) => joined.push(text.text),
1701            ToolResultContent::Json { value } => joined.push(value.to_string()),
1702            image @ ToolResultContent::Image(_) => images.push(image),
1703        }
1704    }
1705    std::iter::once(ToolResultContent::text(joined.join("\n")))
1706        .chain(images)
1707        .collect()
1708}
1709
1710/// Whether a result's `content` is only blank text, or nothing.
1711fn says_nothing(content: &[ToolResultContent]) -> bool {
1712    content.iter().all(|part| match part {
1713        ToolResultContent::Text(text) => text.text.trim().is_empty(),
1714        ToolResultContent::Json { .. } | ToolResultContent::Image(_) => false,
1715    })
1716}
1717
1718/// How many parts of a result's `content` are text or JSON.
1719fn texts(content: &[ToolResultContent]) -> usize {
1720    content
1721        .iter()
1722        .filter(|part| !matches!(part, ToolResultContent::Image(_)))
1723        .count()
1724}
1725
1726/// A tool result as text, for a model without tools.
1727fn result_text(result: &ToolResult) -> String {
1728    let text: Vec<String> = result
1729        .content
1730        .iter()
1731        .map(|part| match part {
1732            ToolResultContent::Text(text) => text.text.clone(),
1733            ToolResultContent::Json { value } => value.to_string(),
1734            ToolResultContent::Image(_) => TOOL_IMAGE_OMITTED.to_owned(),
1735        })
1736        .collect();
1737    let kind = if result.is_error { "error" } else { "result" };
1738    format!("[tool {} {kind}] {}", result.name, text.join("\n"))
1739}
1740
1741/// `content` with each image the result cannot carry replaced: it becomes
1742/// [`TOOL_IMAGE_ATTACHED`] and moves to `attached` when it can go in a user
1743/// message, else [`TOOL_IMAGE_OMITTED`].
1744fn result_images(
1745    content: Vec<ToolResultContent>,
1746    form: &Form<'_>,
1747    attached: &mut Vec<crate::message::Image>,
1748) -> Vec<ToolResultContent> {
1749    let mut shaped: Vec<ToolResultContent> = Vec::with_capacity(content.len());
1750    for part in content {
1751        match part {
1752            ToolResultContent::Image(image) => {
1753                let image = sendable_image(image);
1754                if form.sends(&image, Place::ToolResult) {
1755                    shaped.push(ToolResultContent::Image(image));
1756                    continue;
1757                }
1758                let placeholder = if form.sends(&image, Place::User) {
1759                    attached.push(image);
1760                    TOOL_IMAGE_ATTACHED
1761                } else {
1762                    TOOL_IMAGE_OMITTED
1763                };
1764                if shaped.last().and_then(ToolResultContent::as_text) != Some(placeholder) {
1765                    shaped.push(ToolResultContent::text(placeholder));
1766                }
1767            }
1768            part => shaped.push(part),
1769        }
1770    }
1771    shaped
1772}
1773
1774/// `source` with raw bytes given as base64, which every wire that takes
1775/// inline data reads.
1776fn sendable(source: DocumentSourceKind) -> DocumentSourceKind {
1777    match source {
1778        DocumentSourceKind::Raw(bytes) => DocumentSourceKind::Base64(BASE64_STANDARD.encode(bytes)),
1779        source => source,
1780    }
1781}
1782
1783/// Whether [`sendable_image`] leaves `image` as it is: no raw bytes to
1784/// encode, and no media type its bytes would name.
1785fn sendable_as_is(image: &crate::message::Image) -> bool {
1786    match &image.data {
1787        DocumentSourceKind::Raw(_) => false,
1788        DocumentSourceKind::Base64(data) => image.media_type.is_some() || sniffed(data).is_none(),
1789        _ => true,
1790    }
1791}
1792
1793/// `image` with raw bytes as base64, and, when it is inline data without a
1794/// media type, the type its bytes name.
1795fn sendable_image(mut image: crate::message::Image) -> crate::message::Image {
1796    image.data = sendable(image.data);
1797    if image.media_type.is_none()
1798        && let DocumentSourceKind::Base64(data) = &image.data
1799    {
1800        image.media_type = sniffed(data);
1801    }
1802    image
1803}
1804
1805/// The image type the base64 `data` starts with.
1806fn sniffed(data: &str) -> Option<ImageMediaType> {
1807    let head: String = data.chars().take(24).collect();
1808    let bytes = BASE64_STANDARD
1809        .decode(head.as_bytes())
1810        .or_else(|_| BASE64_STANDARD_NO_PAD.decode(head.as_bytes()))
1811        .ok()?;
1812    ImageMediaType::sniff(&bytes)
1813}
1814
1815/// `history` with each run of adjacent user messages made one: a wire that
1816/// requires alternating roles would otherwise reject it. An empty user
1817/// message stays on its own, for the request boundary to reject.
1818fn merge_users(history: Vec<Message>) -> Vec<Message> {
1819    let mut merged: Vec<Message> = Vec::with_capacity(history.len());
1820    for message in history {
1821        match (merged.last_mut(), message) {
1822            (Some(Message::User { content: previous }), Message::User { content })
1823                if !previous.is_empty() && !content.is_empty() =>
1824            {
1825                previous.extend(content);
1826            }
1827            (_, message) => merged.push(message),
1828        }
1829    }
1830    merged
1831}
1832
1833#[cfg(test)]
1834mod tests;