Skip to main content

rig_core/completion/
request.rs

1//! Completion requests, normalized responses, and provider model contracts.
2//!
3//! ```
4//! use rig_core::completion::CompletionRequest;
5//!
6//! let request = CompletionRequest::new("Who are you?")
7//!     .preamble("You are a concise assistant.")
8//!     .temperature(0.5);
9//! assert_eq!(request.temperature, Some(0.5));
10//! ```
11
12use super::message::{
13    AssistantContent, AssistantMessage, CallId, DocumentMediaType, Origin, StopReason, ToolCall,
14};
15use super::options::{
16    CacheRetention, GenerationOptions, OnUnsupported, Reasoning, ServiceTier, Verbosity,
17};
18use super::provider_options::{ExtensionOptions, ProviderExtension, ProviderOptions, ReplyExtras};
19use crate::error::ProviderError;
20use crate::message::ToolChoice;
21use crate::{
22    json_utils,
23    message::{Message, ToolName, UserContent},
24};
25
26use serde::{Deserialize, Serialize};
27use std::collections::HashMap;
28use std::ops::{Add, AddAssign};
29use std::sync::Arc;
30
31/// A document sent with a request as context, rendered before the chat history.
32#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
33pub struct Document {
34    /// Stable document identifier included in the serialized context block.
35    pub id: String,
36    /// Text content passed to the model as retrieval or static context.
37    pub text: String,
38    /// Additional string metadata rendered before the document text.
39    #[serde(flatten)]
40    pub additional_props: HashMap<String, String>,
41}
42
43impl std::fmt::Display for Document {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(
46            f,
47            concat!("<file id: {}>\n", "{}\n", "</file>\n"),
48            self.id,
49            if self.additional_props.is_empty() {
50                self.text.clone()
51            } else {
52                let mut sorted_props = self.additional_props.iter().collect::<Vec<_>>();
53                sorted_props.sort_by(|a, b| a.0.cmp(b.0));
54                let metadata = sorted_props
55                    .iter()
56                    .map(|(k, v)| format!("{k}: {v:?}"))
57                    .collect::<Vec<_>>()
58                    .join(" ");
59                format!("<metadata {} />\n{}", metadata, self.text)
60            }
61        )
62    }
63}
64
65/// A tool the model may call: its name, description and argument schema,
66/// sent to the model, and what the tool does to the world
67/// ([`ToolAnnotations`]), which stays with the host.
68#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
69pub struct ToolDefinition {
70    /// Tool name exposed to the model. It must match the registered tool name.
71    pub name: ToolName,
72    /// Human-readable description sent to the model.
73    pub description: String,
74    /// JSON Schema describing tool arguments.
75    pub parameters: serde_json::Value,
76    /// What calling the tool does to the world, for the host's policies
77    /// (approval, plan mode, running calls side by side). Never sent to
78    /// the model; written only when something is stated.
79    #[serde(default, skip_serializing_if = "ToolAnnotations::is_empty")]
80    pub annotations: ToolAnnotations,
81}
82
83impl ToolDefinition {
84    /// A tool the model may call by `name`, with arguments matching the
85    /// JSON Schema `parameters`, and no annotations.
86    pub fn new(
87        name: ToolName,
88        description: impl Into<String>,
89        parameters: serde_json::Value,
90    ) -> Self {
91        Self {
92            name,
93            description: description.into(),
94            parameters,
95            annotations: ToolAnnotations::default(),
96        }
97    }
98
99    /// The same definition with `annotations`.
100    pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
101        self.annotations = annotations;
102        self
103    }
104}
105
106/// What calling a tool does to the world, as the Model Context Protocol's
107/// tool annotations state it, and spelled as they are on its wire
108/// (`readOnlyHint`, …), so an MCP tool's annotations read straight into
109/// one. Each is a hint a tool's author gives: a host's policies read them
110/// (approval, plan mode, which calls run side by side), never trusting a
111/// tool it does not trust for them. A hint left unset takes the protocol's
112/// default, which the accessors apply: not read-only, destructive, not
113/// idempotent, open world.
114///
115/// ```
116/// use rig_core::completion::ToolAnnotations;
117///
118/// let read = ToolAnnotations::default().with_read_only(true).with_open_world(false);
119/// assert!(read.read_only() && !read.destructive() && !read.open_world());
120/// let unknown = ToolAnnotations::default();
121/// assert!(!unknown.read_only() && unknown.destructive() && unknown.open_world());
122/// let mcp: ToolAnnotations = serde_json::from_value(serde_json::json!({"readOnlyHint": true}))?;
123/// assert!(mcp.read_only());
124/// # Ok::<(), serde_json::Error>(())
125/// ```
126#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
127#[serde(rename_all = "camelCase")]
128pub struct ToolAnnotations {
129    /// A human-readable title for the tool.
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub title: Option<String>,
132    /// Whether the tool changes nothing in its environment.
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub read_only_hint: Option<bool>,
135    /// Whether a tool that changes things may also destroy or overwrite
136    /// what was there (rather than only add). Meaningless for a read-only
137    /// tool.
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub destructive_hint: Option<bool>,
140    /// Whether calling the tool again with the same arguments has no
141    /// further effect. Meaningless for a read-only tool.
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub idempotent_hint: Option<bool>,
144    /// Whether the tool reaches beyond a closed set of things, such as the
145    /// web or a shell; `false` for one that works on a known store, such
146    /// as the project's files.
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub open_world_hint: Option<bool>,
149}
150
151impl ToolAnnotations {
152    /// Whether nothing is stated.
153    pub fn is_empty(&self) -> bool {
154        *self == Self::default()
155    }
156
157    /// Whether the tool changes nothing: the hint, else `false`.
158    pub fn read_only(&self) -> bool {
159        self.read_only_hint.unwrap_or(false)
160    }
161
162    /// Whether the tool may destroy or overwrite something: never for a
163    /// read-only tool, else the hint, else `true`.
164    pub fn destructive(&self) -> bool {
165        !self.read_only() && self.destructive_hint.unwrap_or(true)
166    }
167
168    /// Whether repeating a call has no further effect: always for a
169    /// read-only tool, else the hint, else `false`.
170    pub fn idempotent(&self) -> bool {
171        self.read_only() || self.idempotent_hint.unwrap_or(false)
172    }
173
174    /// Whether the tool reaches an open world: the hint, else `true`.
175    pub fn open_world(&self) -> bool {
176        self.open_world_hint.unwrap_or(true)
177    }
178
179    /// The same annotations with a title.
180    pub fn with_title(mut self, title: impl Into<String>) -> Self {
181        self.title = Some(title.into());
182        self
183    }
184
185    /// The same annotations stating whether the tool is read-only.
186    pub fn with_read_only(mut self, read_only: bool) -> Self {
187        self.read_only_hint = Some(read_only);
188        self
189    }
190
191    /// The same annotations stating whether the tool is destructive.
192    pub fn with_destructive(mut self, destructive: bool) -> Self {
193        self.destructive_hint = Some(destructive);
194        self
195    }
196
197    /// The same annotations stating whether the tool is idempotent.
198    pub fn with_idempotent(mut self, idempotent: bool) -> Self {
199        self.idempotent_hint = Some(idempotent);
200        self
201    }
202
203    /// The same annotations stating whether the tool reaches an open world.
204    pub fn with_open_world(mut self, open_world: bool) -> Self {
205        self.open_world_hint = Some(open_world);
206        self
207    }
208}
209
210/// Provider-native tool definition.
211///
212/// Stored under `additional_params.tools` and forwarded by providers that support
213/// provider-managed tools.
214#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
215pub struct ProviderToolDefinition {
216    /// Tool type/kind name as expected by the target provider (for example `web_search`).
217    #[serde(rename = "type")]
218    pub kind: String,
219    /// Additional provider-specific configuration for this hosted tool.
220    #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
221    pub config: serde_json::Map<String, serde_json::Value>,
222}
223
224impl ProviderToolDefinition {
225    /// Creates a provider-hosted tool definition by type.
226    pub fn new(kind: impl Into<String>) -> Self {
227        Self {
228            kind: kind.into(),
229            config: serde_json::Map::new(),
230        }
231    }
232
233    /// Adds a provider-specific configuration key/value.
234    pub fn with_config(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
235        self.config.insert(key.into(), value);
236        self
237    }
238}
239
240/// Normalized generation ending. Unmapped provider values remain in [`Self::Other`].
241/// Failure statuses may accompany parseable output; callers must decide whether
242/// such output is usable rather than treating every response as successful.
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
244#[serde(rename_all = "snake_case")]
245#[non_exhaustive]
246pub enum FinishReason {
247    /// Natural end of the response.
248    Stop,
249    /// The response hit the output-token limit.
250    Length,
251    /// The model stopped to call one or more tools.
252    ToolCalls,
253    /// The provider filtered the content.
254    ContentFilter,
255    /// A provider-specific reason outside the normalized vocabulary, carried
256    /// verbatim in the provider's own wire spelling.
257    Other(String),
258}
259
260impl FinishReason {
261    /// Changes [`Self::Stop`] to [`Self::ToolCalls`] when output contains a tool
262    /// call. All other reasons remain unchanged. Response builders and streaming
263    /// aggregation apply this reconciliation.
264    pub fn reconcile_with_output(self, has_tool_call: bool) -> Self {
265        if has_tool_call && matches!(self, Self::Stop) {
266            Self::ToolCalls
267        } else {
268            self
269        }
270    }
271
272    /// Returns whether the reason is [`Self::Length`] or [`Self::ContentFilter`].
273    /// These reasons permit answerless turns without treating absent content as
274    /// a malformed response. Unknown reasons are not classified as truncation.
275    pub fn truncated_output(&self) -> bool {
276        matches!(self, Self::Length | Self::ContentFilter)
277    }
278
279    /// Formats an answerless-turn diagnostic with budget or filtering advice
280    /// for known truncation reasons, and a generic explanation otherwise.
281    pub fn no_answer_message(&self) -> String {
282        let remedy = match self {
283            Self::Length => {
284                "the turn ran out of output budget before producing one — \
285                 raise max_tokens for this request"
286            }
287            Self::ContentFilter => {
288                "the provider filtered the response — the content, not the \
289                 budget, is what it objected to"
290            }
291            _ => "the turn ended before producing one",
292        };
293        format!(
294            "the model produced no answer and stopped with \
295             finish_reason={self:?}; {remedy}"
296        )
297    }
298}
299
300/// Assistant content and normalized completion metadata. The choice may be
301/// empty, including for truncated or filtered turns. Provider-specific data is
302/// available through [`Self::raw`] without retaining a concrete model type.
303///
304/// A response goes straight back into the conversation as the assistant
305/// turn: `history.extend(response.message())`.
306#[non_exhaustive]
307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308#[serde(from = "CompletionResponseRepr")]
309pub struct CompletionResponse {
310    /// Assistant content returned by the provider, one block per provider
311    /// output item, in provider order. Possibly empty.
312    pub choice: Vec<AssistantContent>,
313    /// Tokens used during prompting and responding
314    pub usage: Usage,
315    /// The wire, provider and requested model that produced the response,
316    /// with the model and response id the provider reported.
317    pub origin: Origin,
318    /// The provider's report that the turn failed, such as a refusal's
319    /// explanation. The turn's message then ends in [`StopReason::Error`]
320    /// and is never replayed.
321    #[serde(default, skip_serializing_if = "Option::is_none")]
322    pub error: Option<String>,
323    /// Why the reply stopped before the provider ended it, when the caller
324    /// stopped reading: the turn's message then ends in
325    /// [`StopReason::Aborted`] and is never replayed.
326    #[serde(default, skip_serializing_if = "Option::is_none")]
327    pub aborted: Option<String>,
328    /// Request identifier from HTTP headers or SDK metadata, not the body's
329    /// response ID. `None` when the provider reports none.
330    #[serde(default, skip_serializing_if = "Option::is_none")]
331    pub provider_request_id: Option<String>,
332    /// Reported finish reason, reconciled by the setters with tool-call output.
333    /// Read through [`Self::finish_reason`].
334    #[serde(default)]
335    finish_reason: Option<FinishReason>,
336    /// Whether [`FinishReason::Other`] ends the turn as a normal stop. Read
337    /// through [`Self::accepts_unknown_finish_reasons`].
338    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
339    accepts_unknown_finish_reasons: bool,
340    /// The calls the provider ended the reply in the middle of. Read
341    /// through [`Self::unfinished_calls`].
342    #[serde(default, skip_serializing_if = "Vec::is_empty")]
343    unfinished_calls: Vec<CallId>,
344    /// Provider response document for typed inspection through deserialization.
345    /// Parsed wire types may omit unmodeled fields. This data does not override
346    /// normalized fields; callers constructing responses must supply it.
347    pub raw: serde_json::Value,
348}
349
350/// Distinct response and transport identifiers for one model call.
351/// Unreported identifiers remain `None`.
352#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
353pub struct ResponseIdentity {
354    /// Response-wide ID.
355    #[serde(default, skip_serializing_if = "Option::is_none")]
356    pub response_id: Option<String>,
357    /// Transport request ID from HTTP headers or SDK metadata.
358    #[serde(default, skip_serializing_if = "Option::is_none")]
359    pub provider_request_id: Option<String>,
360}
361
362impl CompletionResponse {
363    /// Create a response from its required parts; optional metadata starts
364    /// unset. `raw` is the provider's own document for this response,
365    /// serialized; see [`Self::raw`].
366    pub fn new(
367        choice: Vec<AssistantContent>,
368        usage: Usage,
369        origin: Origin,
370        raw: serde_json::Value,
371    ) -> Self {
372        Self {
373            choice,
374            usage,
375            origin,
376            error: None,
377            aborted: None,
378            provider_request_id: None,
379            finish_reason: None,
380            accepts_unknown_finish_reasons: false,
381            unfinished_calls: Vec::new(),
382            raw,
383        }
384    }
385
386    /// The provider descriptor name (`"openai"`).
387    pub fn provider(&self) -> &str {
388        &self.origin.provider
389    }
390
391    /// `P`'s typed view of [`Self::raw`]. `None` unless `P` is the provider
392    /// that produced the reply.
393    ///
394    /// # Errors
395    ///
396    /// The inner result fails when `raw` does not hold `P`'s extras.
397    pub fn extras<P: ProviderExtension>(&self) -> Option<Result<P::Extras, serde_json::Error>> {
398        (self.origin.provider == P::PROVIDER)
399            .then(|| P::Extras::from_reply(&self.origin.api, &self.raw))
400    }
401
402    /// `P`'s typed view of [`Self::raw`], or the empty view (every field
403    /// `None`) when it cannot be read: when another provider produced the
404    /// reply, or when `raw` does not hold `P`'s extras. A decode failure is
405    /// logged at `debug`; use [`Self::extras`] to tell the cases apart.
406    pub fn extras_lossy<P: ProviderExtension>(&self) -> P::Extras
407    where
408        P::Extras: Default,
409    {
410        match self.extras::<P>() {
411            Some(Ok(extras)) => extras,
412            Some(Err(error)) => {
413                tracing::debug!(
414                    provider = P::PROVIDER,
415                    api = %self.origin.api,
416                    %error,
417                    "reply extras did not decode; reading them as empty"
418                );
419                P::Extras::default()
420            }
421            None => P::Extras::default(),
422        }
423    }
424
425    /// The model the provider reported, when it reported one.
426    pub fn model(&self) -> Option<&str> {
427        self.origin.response_model.as_deref()
428    }
429
430    /// The provider's response id, when it sent one.
431    pub fn response_id(&self) -> Option<&str> {
432        self.origin.response_id.as_deref()
433    }
434
435    /// Why the model stopped generating, when the provider reported it.
436    pub fn finish_reason(&self) -> Option<FinishReason> {
437        self.finish_reason.clone()
438    }
439
440    /// Whether a finish reason outside the normalized vocabulary ends the
441    /// turn as a normal stop, as the request asked
442    /// ([`CompletionRequest::accept_unknown_finish_reasons`]).
443    ///
444    /// [`CompletionRequest::accept_unknown_finish_reasons`]: field@CompletionRequest::accept_unknown_finish_reasons
445    pub fn accepts_unknown_finish_reasons(&self) -> bool {
446        self.accepts_unknown_finish_reasons
447    }
448
449    /// The tool calls the provider ended the reply in the middle of (their
450    /// arguments as far as they came), in the order they opened, such as a
451    /// call cut by [`FinishReason::Length`]. Empty when every call in
452    /// [`Self::choice`] was finished.
453    pub fn unfinished_calls(&self) -> impl Iterator<Item = &ToolCall> {
454        self.tool_calls()
455            .filter(|call| self.unfinished_calls.contains(&call.id))
456    }
457
458    /// The same response, `calls` (ids of calls in [`Self::choice`]) being
459    /// the ones the provider ended the reply in the middle of.
460    pub fn with_unfinished_calls(mut self, calls: impl IntoIterator<Item = CallId>) -> Self {
461        self.unfinished_calls = calls.into_iter().collect();
462        self
463    }
464
465    /// Accept, or refuse, [`FinishReason::Other`] as a normal stop in
466    /// [`Self::stop`]. Filtered content and a reported failure still fail.
467    pub fn accept_unknown_finish_reasons(mut self, accept: bool) -> Self {
468        self.accepts_unknown_finish_reasons = accept;
469        self
470    }
471
472    /// How the turn ended, for history. It fails closed: only a natural
473    /// stop, the token limit and a stop to call tools are successes. A
474    /// reported failure, filtered content and any finish reason outside
475    /// that set are [`StopReason::Error`], unless the response accepts
476    /// unknown reasons ([`Self::accepts_unknown_finish_reasons`]), which then
477    /// stop like [`FinishReason::Stop`]. A reply the caller stopped reading
478    /// is [`StopReason::Aborted`].
479    pub fn stop(&self) -> StopReason {
480        if let Some(error) = &self.error {
481            return StopReason::Error(error.clone());
482        }
483        if let Some(reason) = &self.aborted {
484            return StopReason::Aborted(reason.clone());
485        }
486        match &self.finish_reason {
487            Some(FinishReason::Length) => StopReason::Length,
488            Some(FinishReason::ToolCalls) => StopReason::ToolUse,
489            Some(FinishReason::ContentFilter) => {
490                StopReason::Error("Provider finish_reason: content_filter".to_owned())
491            }
492            Some(FinishReason::Other(reason)) if !self.accepts_unknown_finish_reasons => {
493                StopReason::Error(format!("Provider finish_reason: {reason}"))
494            }
495            Some(FinishReason::Stop | FinishReason::Other(_)) | None
496                if self.tool_calls().next().is_some() =>
497            {
498                StopReason::ToolUse
499            }
500            Some(FinishReason::Stop | FinishReason::Other(_)) | None => StopReason::Stop,
501        }
502    }
503
504    /// This response's identity metadata as one [`ResponseIdentity`] carrier.
505    pub fn identity(&self) -> ResponseIdentity {
506        ResponseIdentity {
507            response_id: self.origin.response_id.clone(),
508            provider_request_id: self.provider_request_id.clone(),
509        }
510    }
511
512    /// Attach the normalized finish reason, reconciled against the choice via
513    /// [`FinishReason::reconcile_with_output`].
514    pub fn with_finish_reason(self, finish_reason: FinishReason) -> Self {
515        self.with_optional_finish_reason(Some(finish_reason))
516    }
517
518    /// The text parts of [`Self::choice`], concatenated in order.
519    pub fn text(&self) -> String {
520        self.choice
521            .iter()
522            .filter_map(|part| match part {
523                AssistantContent::Text(text) => Some(text.text.as_str()),
524                _ => None,
525            })
526            .collect()
527    }
528
529    /// The reasoning text of [`Self::choice`], concatenated in order.
530    /// Redacted reasoning has no text.
531    pub fn reasoning(&self) -> String {
532        self.choice
533            .iter()
534            .filter_map(|part| match part {
535                AssistantContent::Reasoning(reasoning) => Some(reasoning.text.as_str()),
536                _ => None,
537            })
538            .collect()
539    }
540
541    /// The assistant turn to append to the conversation: [`Self::choice`] in
542    /// order with its origin and stop, or `None` for an
543    /// empty choice.
544    pub fn message(&self) -> Option<Message> {
545        if self.choice.is_empty() {
546            return None;
547        }
548        Some(Message::Assistant(AssistantMessage {
549            content: self.choice.clone(),
550            ..self.head()
551        }))
552    }
553
554    /// The turn this response began, holding `content`, for a runtime that
555    /// cut the reply short and answers its calls itself (an agent rolling a
556    /// turn back over an invalid call). It keeps the origin, and stops to
557    /// call tools, since the runtime answers them. A block keeps its provider
558    /// item only when this response holds the same block with it. A response
559    /// the provider did not end, or one the consumer has not wholly taken,
560    /// holds no provider item, so its turn replays canonically.
561    pub fn continued(&self, content: Vec<AssistantContent>) -> AssistantMessage {
562        let content = content
563            .into_iter()
564            .map(|block| {
565                if self.choice.contains(&block) {
566                    block
567                } else {
568                    crate::operation::completion::canonical(block)
569                }
570            })
571            .collect();
572        AssistantMessage::rolled_back(Some(self.origin.clone()), content)
573    }
574
575    /// The turn's origin and stop with no content, for a
576    /// runtime that carries the content separately.
577    pub fn head(&self) -> AssistantMessage {
578        AssistantMessage {
579            content: Vec::new(),
580            origin: Some(self.origin.clone()),
581            stop: Some(self.stop()),
582        }
583    }
584
585    /// The tool calls in [`Self::choice`], in order.
586    pub fn tool_calls(&self) -> impl Iterator<Item = &ToolCall> {
587        self.choice.iter().filter_map(|part| match part {
588            AssistantContent::ToolCall(call) => Some(call),
589            _ => None,
590        })
591    }
592
593    /// Sets or clears the finish reason, reconciling a present reason with the choice.
594    pub fn with_optional_finish_reason(mut self, finish_reason: Option<FinishReason>) -> Self {
595        let has_tool_call = self
596            .choice
597            .iter()
598            .any(|content| matches!(content, AssistantContent::ToolCall(_)));
599        self.finish_reason =
600            finish_reason.map(|reason| reason.reconcile_with_output(has_tool_call));
601        self
602    }
603}
604
605/// Deserialization shape routed through builders for finish-reason reconciliation
606/// and empty-identifier normalization.
607#[derive(Deserialize)]
608struct CompletionResponseRepr {
609    choice: Vec<AssistantContent>,
610    usage: Usage,
611    origin: Origin,
612    #[serde(default)]
613    error: Option<String>,
614    #[serde(default)]
615    aborted: Option<String>,
616    #[serde(default)]
617    provider_request_id: Option<String>,
618    #[serde(default)]
619    finish_reason: Option<FinishReason>,
620    #[serde(default)]
621    accepts_unknown_finish_reasons: bool,
622    #[serde(default)]
623    unfinished_calls: Vec<CallId>,
624    raw: serde_json::Value,
625}
626
627impl From<CompletionResponseRepr> for CompletionResponse {
628    fn from(repr: CompletionResponseRepr) -> Self {
629        let CompletionResponseRepr {
630            choice,
631            usage,
632            mut origin,
633            error,
634            aborted,
635            provider_request_id,
636            finish_reason,
637            accepts_unknown_finish_reasons,
638            unfinished_calls,
639            raw,
640        } = repr;
641        use crate::provider_response::reported;
642        origin.response_id = reported(origin.response_id);
643        origin.response_model = reported(origin.response_model);
644        let mut response =
645            Self::new(choice, usage, origin, raw).with_optional_finish_reason(finish_reason);
646        response.error = error;
647        response.aborted = aborted;
648        response.provider_request_id = reported(provider_request_id);
649        response.accepts_unknown_finish_reasons = accepts_unknown_finish_reasons;
650        response.unfinished_calls = unfinished_calls;
651        response
652    }
653}
654
655/// The token usage a provider reported for one completion.
656///
657/// Every provider mapping keeps one contract, so the counters read the same
658/// way on every provider:
659///
660/// - `cached_input_tokens + cache_creation_input_tokens <= input_tokens`:
661///   input counts every prompt token, cache reads and writes included.
662/// - `reasoning_tokens <= output_tokens`: output counts every generated
663///   token, reasoning included.
664/// - `total_tokens == input_tokens + output_tokens`, absent unless both are
665///   reported.
666///
667/// A counter the provider did not send is `None`; a reported zero is
668/// `Some(0)`. Serialized as the same keys, absent when `None`.
669///
670/// ```
671/// use rig_core::completion::Usage;
672///
673/// let usage = Usage::new().input_tokens(12).output_tokens(3).total_tokens(15);
674/// assert_eq!(usage.input_tokens, Some(12));
675/// assert!(usage.cost.is_none());
676/// ```
677#[non_exhaustive]
678#[derive(Debug, Default, PartialEq, Clone, Copy, Serialize, Deserialize)]
679pub struct Usage {
680    /// Every input token of the request: uncached, read from a cache,
681    /// written to a cache, and any prompt a provider's hosted tools added.
682    #[serde(default, skip_serializing_if = "Option::is_none")]
683    pub input_tokens: Option<u64>,
684    /// Every output token, reasoning included.
685    #[serde(default, skip_serializing_if = "Option::is_none")]
686    pub output_tokens: Option<u64>,
687    /// `input_tokens + output_tokens`; absent unless both are reported.
688    #[serde(default, skip_serializing_if = "Option::is_none")]
689    pub total_tokens: Option<u64>,
690    /// The part of `input_tokens` read from a provider-managed cache.
691    #[serde(default, skip_serializing_if = "Option::is_none")]
692    pub cached_input_tokens: Option<u64>,
693    /// The part of `input_tokens` written to a provider-managed cache.
694    #[serde(default, skip_serializing_if = "Option::is_none")]
695    pub cache_creation_input_tokens: Option<u64>,
696    /// The part of `input_tokens` a provider's hosted tools added to the prompt.
697    #[serde(default, skip_serializing_if = "Option::is_none")]
698    pub tool_use_prompt_tokens: Option<u64>,
699    /// The part of `output_tokens` spent on internal reasoning ("thinking",
700    /// "thoughts").
701    #[serde(default, skip_serializing_if = "Option::is_none")]
702    pub reasoning_tokens: Option<u64>,
703    /// What the turn cost in USD, when known. Never derived from the token
704    /// counters here, and none of them is derived from it. A cost the
705    /// provider reports is its figure. One priced from the model's catalog
706    /// spec ([`Pricing::cost`](crate::catalog::Pricing::cost), from the
707    /// facts the wire was connected with) is the
708    /// standard-tier list price of the counted tokens: it leaves out the
709    /// service tier, long-context price tiers and hosted-tool fees (web
710    /// search, code execution), so it can be lower than the bill.
711    #[serde(default, skip_serializing_if = "Option::is_none")]
712    pub cost: Option<Cost>,
713}
714
715impl Usage {
716    /// No counter and no cost reported.
717    pub const fn new() -> Self {
718        Self {
719            input_tokens: None,
720            output_tokens: None,
721            total_tokens: None,
722            cached_input_tokens: None,
723            cache_creation_input_tokens: None,
724            tool_use_prompt_tokens: None,
725            reasoning_tokens: None,
726            cost: None,
727        }
728    }
729
730    /// Whether the provider reported any counter or a cost.
731    pub fn is_reported(&self) -> bool {
732        *self != Self::default()
733    }
734
735    /// The tokens the call read and wrote, what a conversation's next
736    /// request sends again: [`Self::total_tokens`](field@Self::total_tokens),
737    /// else the input and output tokens. `None` without input tokens.
738    pub fn context_tokens(&self) -> Option<u64> {
739        self.total_tokens.or_else(|| {
740            Some(
741                self.input_tokens?
742                    .saturating_add(self.output_tokens.unwrap_or(0)),
743            )
744        })
745    }
746
747    /// Set, or with `None` clear, [`Self::input_tokens`](field@Self::input_tokens).
748    pub fn input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
749        self.input_tokens = tokens.into();
750        self
751    }
752
753    /// Set, or with `None` clear, [`Self::output_tokens`](field@Self::output_tokens).
754    pub fn output_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
755        self.output_tokens = tokens.into();
756        self
757    }
758
759    /// Set, or with `None` clear, [`Self::total_tokens`](field@Self::total_tokens).
760    pub fn total_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
761        self.total_tokens = tokens.into();
762        self
763    }
764
765    /// Set, or with `None` clear,
766    /// [`Self::cached_input_tokens`](field@Self::cached_input_tokens).
767    pub fn cached_input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
768        self.cached_input_tokens = tokens.into();
769        self
770    }
771
772    /// Set, or with `None` clear,
773    /// [`Self::cache_creation_input_tokens`](field@Self::cache_creation_input_tokens).
774    pub fn cache_creation_input_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
775        self.cache_creation_input_tokens = tokens.into();
776        self
777    }
778
779    /// Set, or with `None` clear,
780    /// [`Self::tool_use_prompt_tokens`](field@Self::tool_use_prompt_tokens).
781    pub fn tool_use_prompt_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
782        self.tool_use_prompt_tokens = tokens.into();
783        self
784    }
785
786    /// Set, or with `None` clear, [`Self::reasoning_tokens`](field@Self::reasoning_tokens).
787    pub fn reasoning_tokens(mut self, tokens: impl Into<Option<u64>>) -> Self {
788        self.reasoning_tokens = tokens.into();
789        self
790    }
791
792    /// Set, or with `None` clear, [`Self::cost`](field@Self::cost).
793    pub fn cost(mut self, cost: impl Into<Option<Cost>>) -> Self {
794        self.cost = cost.into();
795        self
796    }
797}
798
799/// What one or more turns cost, in USD. `total` is what was charged, or
800/// when [`Self::is_complete`] is `false`, a lower bound of it.
801///
802/// A part is `None` when its source does not give it: a provider that
803/// reports only a total leaves every part `None`, and one that reports
804/// input and output leaves the cache parts `None`. A cost computed from
805/// the catalog ([`Pricing::cost`](crate::catalog::Pricing::cost)) leaves a
806/// cache part `None` when the catalog lists no rate for it and the usage
807/// has such tokens; its `total` then sums the known parts only, and it is
808/// not complete. A part is never filled with a placeholder `0.0`. Summing
809/// costs keeps a part only when every side has it, and is complete only
810/// when every side is. A `None` part is left out when serialized, and an
811/// absent one reads back as `None`.
812#[non_exhaustive]
813#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
814pub struct Cost {
815    /// Uncached input tokens.
816    #[serde(default, skip_serializing_if = "Option::is_none")]
817    pub input: Option<f64>,
818    /// Output tokens, reasoning included.
819    #[serde(default, skip_serializing_if = "Option::is_none")]
820    pub output: Option<f64>,
821    /// Input tokens read from a cache.
822    #[serde(default, skip_serializing_if = "Option::is_none")]
823    pub cache_read: Option<f64>,
824    /// Input tokens written to a cache.
825    #[serde(default, skip_serializing_if = "Option::is_none")]
826    pub cache_write: Option<f64>,
827    /// The whole charge, or a lower bound of it when the cost is not
828    /// complete.
829    pub total: f64,
830    /// Whether a part with tokens had no price, so `total` leaves it out.
831    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
832    incomplete: bool,
833}
834
835impl Cost {
836    /// Whether [`Self::total`](field@Self::total) is the whole charge.
837    /// `false` when a part the usage has tokens for had no known price, so
838    /// `total` is a lower bound.
839    pub fn is_complete(&self) -> bool {
840        !self.incomplete
841    }
842
843    /// A cost from the catalog's prices, where `None` marks a part whose
844    /// tokens had no price: `total` sums the known parts, and the cost is
845    /// complete only when no part is `None`.
846    pub(crate) fn priced(
847        input: f64,
848        output: f64,
849        cache_read: Option<f64>,
850        cache_write: Option<f64>,
851    ) -> Self {
852        Self {
853            input: Some(input),
854            output: Some(output),
855            cache_read,
856            cache_write,
857            total: input + output + cache_read.unwrap_or(0.0) + cache_write.unwrap_or(0.0),
858            incomplete: cache_read.is_none() || cache_write.is_none(),
859        }
860    }
861
862    /// A cost split into its parts, each known; `total` is their sum.
863    pub fn from_parts(input: f64, output: f64, cache_read: f64, cache_write: f64) -> Self {
864        Self {
865            input: Some(input),
866            output: Some(output),
867            cache_read: Some(cache_read),
868            cache_write: Some(cache_write),
869            total: input + output + cache_read + cache_write,
870            incomplete: false,
871        }
872    }
873
874    /// A cost known only as its total; every part is `None`.
875    pub fn from_total(total: f64) -> Self {
876        Self {
877            input: None,
878            output: None,
879            cache_read: None,
880            cache_write: None,
881            total,
882            incomplete: false,
883        }
884    }
885
886    /// Set, or with `None` clear, [`Self::input`](field@Self::input).
887    /// `total` is left as it is.
888    pub fn input(mut self, input: impl Into<Option<f64>>) -> Self {
889        self.input = input.into();
890        self
891    }
892
893    /// Set, or with `None` clear, [`Self::output`](field@Self::output).
894    /// `total` is left as it is.
895    pub fn output(mut self, output: impl Into<Option<f64>>) -> Self {
896        self.output = output.into();
897        self
898    }
899
900    /// Set, or with `None` clear, [`Self::cache_read`](field@Self::cache_read).
901    /// `total` is left as it is.
902    pub fn cache_read(mut self, cache_read: impl Into<Option<f64>>) -> Self {
903        self.cache_read = cache_read.into();
904        self
905    }
906
907    /// Set, or with `None` clear, [`Self::cache_write`](field@Self::cache_write).
908    /// `total` is left as it is.
909    pub fn cache_write(mut self, cache_write: impl Into<Option<f64>>) -> Self {
910        self.cache_write = cache_write.into();
911        self
912    }
913}
914
915/// Sum one part of two costs: unknown when either side's is unknown, since
916/// a known part plus an unknown one is not known.
917fn add_part(lhs: Option<f64>, rhs: Option<f64>) -> Option<f64> {
918    Some(lhs? + rhs?)
919}
920
921/// `total` always sums. Each part sums only when both sides know it, and is
922/// `None` otherwise, so a summed part never understates what it covers. The
923/// sum is complete only when both sides are.
924impl Add for Cost {
925    type Output = Self;
926
927    fn add(self, other: Self) -> Self::Output {
928        Self {
929            input: add_part(self.input, other.input),
930            output: add_part(self.output, other.output),
931            cache_read: add_part(self.cache_read, other.cache_read),
932            cache_write: add_part(self.cache_write, other.cache_write),
933            total: self.total + other.total,
934            incomplete: self.incomplete || other.incomplete,
935        }
936    }
937}
938
939/// Sum two counters where an unreported side does not turn a reported one
940/// into "unreported".
941fn add_counter(lhs: Option<u64>, rhs: Option<u64>) -> Option<u64> {
942    match (lhs, rhs) {
943        (None, None) => None,
944        (lhs, rhs) => Some(lhs.unwrap_or(0) + rhs.unwrap_or(0)),
945    }
946}
947
948impl Add for Usage {
949    type Output = Self;
950
951    fn add(mut self, other: Self) -> Self::Output {
952        self += other;
953        self
954    }
955}
956
957/// Token counters add where an unreported side adds nothing. Cost sums only
958/// when both sides have one: a turn whose cost is unknown makes the sum
959/// unknown, rather than too low. A side that reports nothing at all
960/// ([`Usage::is_reported`] is `false`) is the identity, so a fold from
961/// [`Usage::default`] keeps its first turn's cost.
962impl AddAssign for Usage {
963    fn add_assign(&mut self, other: Self) {
964        if !other.is_reported() {
965            return;
966        }
967        if !self.is_reported() {
968            *self = other;
969            return;
970        }
971        self.cost = match (self.cost, other.cost) {
972            (Some(lhs), Some(rhs)) => Some(lhs + rhs),
973            _ => None,
974        };
975        self.input_tokens = add_counter(self.input_tokens, other.input_tokens);
976        self.output_tokens = add_counter(self.output_tokens, other.output_tokens);
977        self.total_tokens = add_counter(self.total_tokens, other.total_tokens);
978        self.cached_input_tokens = add_counter(self.cached_input_tokens, other.cached_input_tokens);
979        self.cache_creation_input_tokens = add_counter(
980            self.cache_creation_input_tokens,
981            other.cache_creation_input_tokens,
982        );
983        self.tool_use_prompt_tokens =
984            add_counter(self.tool_use_prompt_tokens, other.tool_use_prompt_tokens);
985        self.reasoning_tokens = add_counter(self.reasoning_tokens, other.reasoning_tokens);
986    }
987}
988
989/// Many calls' [`Usage`] summed for a running account: the token counters,
990/// the known cost, and how many calls the sum holds and how many of them
991/// were not fully priced.
992///
993/// ```
994/// use rig_core::completion::{Usage, UsageTotals};
995///
996/// let mut totals = UsageTotals::default();
997/// totals.record(&Usage::new().input_tokens(100).output_tokens(20).total_tokens(120));
998/// assert_eq!((totals.calls, totals.unpriced, totals.uncached_input()), (1, 1, 100));
999/// ```
1000#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
1001pub struct UsageTotals {
1002    /// The token counters summed, with no cost: a call that did not
1003    /// report a counter adds nothing to it.
1004    pub tokens: Usage,
1005    /// The known costs summed, in USD.
1006    pub cost: f64,
1007    /// The calls whose cost is not known, or known only in part, so
1008    /// [`Self::cost`](field@Self::cost) is a lower bound when any.
1009    pub unpriced: u64,
1010    /// The calls summed.
1011    pub calls: u64,
1012}
1013
1014impl UsageTotals {
1015    /// Adds a finished call's `usage`.
1016    pub fn record(&mut self, usage: &Usage) {
1017        self.calls += 1;
1018        match usage.cost {
1019            Some(cost) => {
1020                self.cost += cost.total;
1021                if !cost.is_complete() {
1022                    self.unpriced += 1;
1023                }
1024            }
1025            None => self.unpriced += 1,
1026        }
1027        self.tokens += usage.cost(None);
1028    }
1029
1030    /// Adds what `other` summed.
1031    pub fn add(&mut self, other: &UsageTotals) {
1032        self.tokens += other.tokens;
1033        self.cost += other.cost;
1034        self.unpriced += other.unpriced;
1035        self.calls += other.calls;
1036    }
1037
1038    /// Input tokens neither read from nor written to a cache.
1039    pub fn uncached_input(&self) -> u64 {
1040        self.tokens
1041            .input_tokens
1042            .unwrap_or(0)
1043            .saturating_sub(self.tokens.cached_input_tokens.unwrap_or(0))
1044            .saturating_sub(self.tokens.cache_creation_input_tokens.unwrap_or(0))
1045    }
1046
1047    /// The tokens read and written, cached input included.
1048    pub fn total_tokens(&self) -> u64 {
1049        self.tokens
1050            .input_tokens
1051            .unwrap_or(0)
1052            .saturating_add(self.tokens.output_tokens.unwrap_or(0))
1053    }
1054
1055    /// The cost as `$0.123`, ending in `+` when some calls were not priced,
1056    /// or `None` when no call was.
1057    pub fn cost_label(&self) -> Option<String> {
1058        if self.unpriced >= self.calls {
1059            return None;
1060        }
1061        let more = if self.unpriced > 0 { "+" } else { "" };
1062        Some(format!("{}{more}", dollars_label(self.cost)))
1063    }
1064
1065    /// The [cost](Self::cost_label), or as `1.2M tokens` when no call was
1066    /// priced, such as a local or subscription model's.
1067    pub fn cost_or_tokens(&self) -> String {
1068        self.cost_label()
1069            .unwrap_or_else(|| format!("{} tokens", tokens_label(self.total_tokens())))
1070    }
1071}
1072
1073/// One line for a person or a log: the calls, the tokens by kind and the
1074/// cost.
1075impl std::fmt::Display for UsageTotals {
1076    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1077        match self.calls {
1078            1 => f.write_str("1 model call")?,
1079            calls => write!(f, "{calls} model calls")?,
1080        }
1081        write!(f, ": {} in", tokens_label(self.uncached_input()))?;
1082        if let Some(read) = self.tokens.cached_input_tokens.filter(|read| *read > 0) {
1083            write!(f, ", {} cache read", tokens_label(read))?;
1084        }
1085        if let Some(written) = self
1086            .tokens
1087            .cache_creation_input_tokens
1088            .filter(|written| *written > 0)
1089        {
1090            write!(f, ", {} cache written", tokens_label(written))?;
1091        }
1092        write!(
1093            f,
1094            ", {} out",
1095            tokens_label(self.tokens.output_tokens.unwrap_or(0))
1096        )?;
1097        if let Some(thought) = self.tokens.reasoning_tokens.filter(|thought| *thought > 0) {
1098            write!(f, " ({} reasoning)", tokens_label(thought))?;
1099        }
1100        let cost = self.cost_label();
1101        write!(f, "; {}", cost.as_deref().unwrap_or("cost unknown"))
1102    }
1103}
1104
1105/// The context a conversation's next request sends, against the model's
1106/// window.
1107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1108pub struct ContextUse {
1109    /// Tokens in use.
1110    pub tokens: u64,
1111    /// The model's window, when known.
1112    pub window: Option<u32>,
1113}
1114
1115impl ContextUse {
1116    /// The share of the window in use, in percent, when the window is known.
1117    pub fn percent(&self) -> Option<u64> {
1118        self.window
1119            .filter(|window| *window > 0)
1120            .map(|window| self.tokens.saturating_mul(100) / u64::from(window))
1121    }
1122}
1123
1124/// `45k/200k (22%)`, or `45k` when the window is not known.
1125impl std::fmt::Display for ContextUse {
1126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1127        match (self.window, self.percent()) {
1128            (Some(window), Some(percent)) => write!(
1129                f,
1130                "{}/{} ({percent}%)",
1131                tokens_label(self.tokens),
1132                tokens_label(u64::from(window))
1133            ),
1134            _ => f.write_str(&tokens_label(self.tokens)),
1135        }
1136    }
1137}
1138
1139/// A token count in at most four characters plus a unit: `999`, `1.2k`,
1140/// `45k`, `1.2M`.
1141pub fn tokens_label(count: u64) -> String {
1142    // Shown rounded, so the float conversion's precision does not matter.
1143    let scaled = |unit: f64| count as f64 / unit;
1144    match count {
1145        0..1_000 => count.to_string(),
1146        1_000..10_000 => format!("{:.1}k", scaled(1e3)),
1147        10_000..1_000_000 => format!("{:.0}k", scaled(1e3)),
1148        1_000_000..10_000_000 => format!("{:.1}M", scaled(1e6)),
1149        _ => format!("{:.0}M", scaled(1e6)),
1150    }
1151}
1152
1153/// A cost in USD: tenths of a cent below a dollar, cents above.
1154fn dollars_label(cost: f64) -> String {
1155    if cost < 1.0 {
1156        format!("${cost:.3}")
1157    } else {
1158        format!("${cost:.2}")
1159    }
1160}
1161
1162/// Model capabilities used by runtimes when preparing requests.
1163/// Defaults are conservative; construct through [`Self::new`] and setters.
1164#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1165pub struct ProviderCapabilities {
1166    /// Whether native structured output can remain enabled with tool calls
1167    /// without suppressing them. Defaults to `false`.
1168    pub composes_native_output_with_tools: bool,
1169    /// Whether the model answers a forced tool choice (`Required` or
1170    /// `Specific`) with an error. Rig then does not force the output tool
1171    /// of its own structured-output flow (the extractor). Defaults to `false`.
1172    #[serde(default, skip_serializing_if = "crate::json_utils::is_false")]
1173    pub rejects_forced_tool_choice: bool,
1174}
1175
1176impl ProviderCapabilities {
1177    /// Create the conservative capability set used by default.
1178    pub const fn new() -> Self {
1179        Self {
1180            composes_native_output_with_tools: false,
1181            rejects_forced_tool_choice: false,
1182        }
1183    }
1184
1185    /// Declare whether native structured output composes with tool calls.
1186    pub const fn with_native_output_tool_composition(mut self, supported: bool) -> Self {
1187        self.composes_native_output_with_tools = supported;
1188        self
1189    }
1190
1191    /// Declare whether the model rejects a forced tool choice.
1192    pub const fn with_forced_tool_choice_rejected(mut self, rejected: bool) -> Self {
1193        self.rejects_forced_tool_choice = rejected;
1194        self
1195    }
1196}
1197
1198/// A completion request to any provider's model.
1199#[non_exhaustive]
1200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1201pub struct CompletionRequest {
1202    /// Optional model override for this request.
1203    pub model: Option<String>,
1204    /// The chat history to be sent to the completion model provider.
1205    /// The very last message is the prompt.
1206    ///
1207    /// It must hold at least one message, and every user and assistant
1208    /// message must carry content. The field is public, so this is a rule
1209    /// rather than a type guarantee: [`Self::validate_message_content`]
1210    /// checks it at the request boundary.
1211    ///
1212    /// Each message is shared, with the conversation it came from and the
1213    /// records of the request, so copying a request copies pointers.
1214    pub chat_history: Vec<Arc<Message>>,
1215    /// The documents to be sent to the completion model provider
1216    pub documents: Vec<Document>,
1217    /// The tools to be sent to the completion model provider, each shared,
1218    /// so copying a request copies pointers.
1219    pub tools: Vec<Arc<ToolDefinition>>,
1220    /// The temperature to be sent to the completion model provider
1221    pub temperature: Option<f64>,
1222    /// The max tokens to be sent to the completion model provider
1223    pub max_tokens: Option<u64>,
1224    /// Which tools the model may or must call before it answers.
1225    pub tool_choice: Option<ToolChoice>,
1226    /// Additional provider-specific parameters to be sent to the completion model provider
1227    pub additional_params: Option<serde_json::Value>,
1228    /// Optional JSON Schema for structured output. When set, providers that support
1229    /// native structured outputs will constrain the model's response to match this schema.
1230    pub output_schema: Option<schemars::Schema>,
1231    /// Opt-in for sensitive request, response, and tool-content telemetry.
1232    /// Defaults to `false` and is excluded from serialization. Enabling it can
1233    /// expose prompts, context, tool results, and model output in span attributes
1234    /// and increase telemetry storage costs. Requires explicit caller consent.
1235    /// Agent drivers record normalized content; direct provider coverage varies,
1236    /// especially for streams consumed after the provider returns.
1237    #[serde(skip)]
1238    pub record_telemetry_content: bool,
1239    /// Whether a finish reason outside the normalized vocabulary
1240    /// ([`FinishReason::Other`]) ends the turn as a normal stop instead of a
1241    /// failure. Defaults to `false`. The response carries the choice, so its
1242    /// [`CompletionResponse::stop`], the runtimes and history replay agree.
1243    /// `Other` also holds genuine failures, such as a malformed tool call or a
1244    /// recitation block, so with this set the tool calls of such a reply run.
1245    /// [`FinishReason::ContentFilter`] still fails the turn.
1246    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1247    pub accept_unknown_finish_reasons: bool,
1248    /// Send other models' reasoning in the history as reasoning where the
1249    /// target reads it ([`Accepts::foreign_reasoning`]), instead of as plain
1250    /// text. Defaults to `false`.
1251    ///
1252    /// [`Accepts::foreign_reasoning`]: crate::completion::Accepts::foreign_reasoning
1253    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1254    pub foreign_reasoning: bool,
1255    /// Portable generation options. Precedence, lowest first: the mapped
1256    /// options, then [`Self::provider_options`], then `additional_params`.
1257    #[serde(default, skip_serializing_if = "GenerationOptions::is_default")]
1258    pub options: GenerationOptions,
1259    /// Typed per-provider options. The wire reads only the entry of its own
1260    /// provider, above the mapped options and below `additional_params`.
1261    #[serde(default, skip_serializing_if = "ProviderOptions::is_empty")]
1262    pub provider_options: ProviderOptions,
1263    /// Where a provider that caches prompts by marker (Anthropic's Messages
1264    /// family) may mark the end of a stable prefix that later requests
1265    /// repeat, beside its own markers, so content after it (per-turn
1266    /// memory, a reminder) does not cost the cached prefix. A provider that
1267    /// takes fewer markers keeps the points furthest into the request; one
1268    /// that caches prefixes without markers ignores them. Empty marks
1269    /// nothing.
1270    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1271    pub cache_points: Vec<CachePoint>,
1272}
1273
1274/// A point in a [`CompletionRequest`] that ends a stable prefix, for
1275/// [`CompletionRequest::cache_points`]. Prefixes run in the order a request
1276/// is sent: tools, then the system prompt, then the messages.
1277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1278#[serde(rename_all = "snake_case")]
1279pub enum CachePoint {
1280    /// After the tool definitions.
1281    Tools,
1282    /// After the system prompt.
1283    System,
1284    /// After the first `n` user and assistant messages. System messages
1285    /// are not counted, wherever they sit; `Messages(0)` is [`Self::System`].
1286    /// The point names the request's messages as written: adapting them
1287    /// for a wire, which drops some and joins adjacent user messages, moves
1288    /// the point with them, inside a joined message when it ends there.
1289    Messages(usize),
1290    /// After the first `items` content items of the user or assistant
1291    /// message at `message`, counted as [`Self::Messages`] counts them.
1292    Within {
1293        /// The message, from 0, system messages not counted.
1294        message: usize,
1295        /// How many of its content items the prefix holds.
1296        items: usize,
1297    },
1298}
1299
1300impl CompletionRequest {
1301    /// The system instructions of this request: the content of the leading
1302    /// [`Message::System`] in `chat_history`, which is where
1303    /// [`Self::preamble`] places it.
1304    pub fn system_instructions(&self) -> Option<&str> {
1305        match self.chat_history.first().map(Arc::as_ref) {
1306            Some(Message::System { content }) => Some(content.as_str()),
1307            _ => None,
1308        }
1309    }
1310
1311    /// Reject a request with no messages, a user or assistant message with no
1312    /// content, or a tool result with no content blocks. The error is
1313    /// [`ProviderError::Request`] and names the role and index of the first
1314    /// offending message.
1315    ///
1316    /// Every wire rejects an empty turn, so this turns a remote 400 into a
1317    /// local error. It checks the request direction only: a provider may
1318    /// return empty assistant content, which the reply keeps and the
1319    /// runtime judges. `System` content is a
1320    /// `String` and is not checked. A tool result holding one empty text
1321    /// block is not empty.
1322    ///
1323    /// [`Model::call`](crate::driver::Model::call),
1324    /// [`Model::stream`](crate::driver::Model::stream) and their `_observed`
1325    /// twins run it before encoding, so it covers
1326    /// [`DynModel`](crate::DynModel), every model a handler serves and the
1327    /// agent runtimes built on them. The OpenAI Responses websocket session
1328    /// sends without the driver and runs it on each send. Code that encodes
1329    /// a request some other way should call it first.
1330    pub fn validate_message_content(&self) -> Result<(), ProviderError> {
1331        if self.chat_history.is_empty() {
1332            return Err(ProviderError::request(
1333                "request has an empty chat history; providers require at least one message",
1334            ));
1335        }
1336
1337        let empty_message = |role: &str, index: usize| {
1338            ProviderError::request(format!(
1339                "{role} message at index {index} has no content; \
1340                 providers reject empty content blocks"
1341            ))
1342        };
1343
1344        for (index, message) in self.chat_history.iter().enumerate() {
1345            match &**message {
1346                Message::System { .. } => {}
1347                Message::Assistant(AssistantMessage { content, .. }) => {
1348                    if content.is_empty() {
1349                        return Err(empty_message("assistant", index));
1350                    }
1351                }
1352                Message::User { content } => {
1353                    if content.is_empty() {
1354                        return Err(empty_message("user", index));
1355                    }
1356                }
1357            }
1358        }
1359
1360        Ok(())
1361    }
1362
1363    /// The output schema's `"title"`, else `"response_schema"`: a name for
1364    /// providers that need one beside the JSON Schema (such as OpenAI).
1365    pub fn output_schema_name(&self) -> Option<String> {
1366        self.output_schema.as_ref().map(|schema| {
1367            schema
1368                .as_object()
1369                .and_then(|o| o.get("title"))
1370                .and_then(|v| v.as_str())
1371                .unwrap_or("response_schema")
1372                .to_string()
1373        })
1374    }
1375
1376    /// Returns documents normalized into a message (if any).
1377    /// Most providers do not accept documents directly as input, so it needs to convert into a
1378    /// `Message` so that it can be incorporated into `chat_history`.
1379    pub fn normalized_documents(&self) -> Option<Message> {
1380        Self::normalized_documents_from(&self.documents)
1381    }
1382
1383    fn normalized_documents_from(documents: &[Document]) -> Option<Message> {
1384        if documents.is_empty() {
1385            return None;
1386        }
1387
1388        let content = documents
1389            .iter()
1390            .map(|doc| UserContent::document_text(doc.to_string(), Some(DocumentMediaType::TXT)))
1391            .collect();
1392
1393        Some(Message::User { content })
1394    }
1395}
1396
1397/// Place the documents message `message` at the first non-system position,
1398/// so document context lands after any leading system messages. A user
1399/// message already there takes the documents at its front instead, so the
1400/// history keeps alternating roles. Telemetry and the sent request must
1401/// agree on this placement.
1402pub(crate) fn insert_after_leading_system(chat_history: &mut Vec<Arc<Message>>, message: Message) {
1403    let insert_at = chat_history
1404        .iter()
1405        .position(|message| !matches!(&**message, Message::System { .. }))
1406        .unwrap_or(chat_history.len());
1407    let joins =
1408        |at: &&mut Arc<Message>| matches!(&***at, Message::User { content } if !content.is_empty());
1409    match (
1410        chat_history
1411            .get_mut(insert_at)
1412            .filter(joins)
1413            .map(Arc::make_mut),
1414        message,
1415    ) {
1416        (Some(Message::User { content }), Message::User { content: documents }) => {
1417            content.splice(0..0, documents);
1418        }
1419        (_, message) => chat_history.insert(insert_at, Arc::new(message)),
1420    }
1421}
1422
1423fn merge_provider_tools_into_additional_params(
1424    additional_params: Option<serde_json::Value>,
1425    provider_tools: Vec<ProviderToolDefinition>,
1426) -> Option<serde_json::Value> {
1427    if provider_tools.is_empty() {
1428        return additional_params;
1429    }
1430
1431    let mut provider_tools_json = provider_tools
1432        .into_iter()
1433        .map(|ProviderToolDefinition { kind, mut config }| {
1434            // Force the provider tool type from the strongly-typed field.
1435            config.insert("type".to_string(), serde_json::Value::String(kind));
1436            serde_json::Value::Object(config)
1437        })
1438        .collect::<Vec<_>>();
1439
1440    let mut params_map = match additional_params {
1441        Some(serde_json::Value::Object(map)) => map,
1442        Some(serde_json::Value::Bool(stream)) => {
1443            let mut map = serde_json::Map::new();
1444            map.insert("stream".to_string(), serde_json::Value::Bool(stream));
1445            map
1446        }
1447        _ => serde_json::Map::new(),
1448    };
1449
1450    let mut merged_tools = match params_map.shift_remove("tools") {
1451        Some(serde_json::Value::Array(existing)) => existing,
1452        _ => Vec::new(),
1453    };
1454    merged_tools.append(&mut provider_tools_json);
1455    params_map.insert("tools".to_string(), serde_json::Value::Array(merged_tools));
1456    Some(serde_json::Value::Object(params_map))
1457}
1458
1459impl CompletionRequest {
1460    /// A request whose conversation is the one user message `prompt`, with
1461    /// no preamble, documents or tools. The setters below add to it and
1462    /// check nothing; [`Self::validate_message_content`] checks the content
1463    /// when the request is sent.
1464    ///
1465    /// Each setter changes the request's public fields as it is called, so
1466    /// order matters where two setters touch the same field: a second
1467    /// [`Self::preamble`] adds a second system message, and
1468    /// [`Self::additional_params`] with a `tools` key (or `None`) replaces
1469    /// provider tools added before it. Set `additional_params` first.
1470    ///
1471    /// ```
1472    /// use rig_core::completion::CompletionRequest;
1473    ///
1474    /// let request = CompletionRequest::new("Who are you?")
1475    ///     .preamble("You are a concise assistant.")
1476    ///     .temperature(0.5);
1477    /// assert_eq!(request.chat_history.len(), 2);
1478    /// assert_eq!(request.temperature, Some(0.5));
1479    /// ```
1480    pub fn new(prompt: impl Into<Message>) -> Self {
1481        Self::conversation(vec![Arc::new(prompt.into())])
1482    }
1483
1484    /// A request for `chat_history` as given, with nothing else set.
1485    fn conversation(chat_history: Vec<Arc<Message>>) -> Self {
1486        Self {
1487            model: None,
1488            chat_history,
1489            documents: Vec::new(),
1490            tools: Vec::new(),
1491            temperature: None,
1492            max_tokens: None,
1493            tool_choice: None,
1494            additional_params: None,
1495            output_schema: None,
1496            record_telemetry_content: false,
1497            accept_unknown_finish_reasons: false,
1498            foreign_reasoning: false,
1499            options: GenerationOptions::default(),
1500            provider_options: ProviderOptions::default(),
1501            cache_points: Vec::new(),
1502        }
1503    }
1504
1505    /// Put `preamble` first in the conversation, as a [`Message::System`],
1506    /// ahead of any system message already there.
1507    pub fn preamble(mut self, preamble: impl Into<String>) -> Self {
1508        self.chat_history
1509            .insert(0, Arc::new(Message::system(preamble.into())));
1510        self
1511    }
1512
1513    /// Override the model for this request.
1514    pub fn model<S: Into<String>>(mut self, model: impl Into<Option<S>>) -> Self {
1515        self.model = model.into().map(Into::into);
1516        self.warn_if_shadowed("model", self.model.is_some());
1517        self
1518    }
1519
1520    /// Add `message` to the conversation, before the prompt (its last
1521    /// message).
1522    pub fn message(self, message: impl Into<Arc<Message>>) -> Self {
1523        self.messages([message])
1524    }
1525
1526    /// Add `messages` to the conversation in order, before the prompt (its
1527    /// last message).
1528    pub fn messages(mut self, messages: impl IntoIterator<Item = impl Into<Arc<Message>>>) -> Self {
1529        let prompt = self.chat_history.pop();
1530        self.chat_history
1531            .extend(messages.into_iter().map(Into::into));
1532        self.chat_history.extend(prompt);
1533        self
1534    }
1535
1536    /// Add a document.
1537    pub fn document(mut self, document: Document) -> Self {
1538        self.documents.push(document);
1539        self
1540    }
1541
1542    /// Add documents in order.
1543    pub fn documents(mut self, documents: impl IntoIterator<Item = Document>) -> Self {
1544        self.documents.extend(documents);
1545        self
1546    }
1547
1548    /// Add a tool.
1549    pub fn tool(self, tool: impl Into<Arc<ToolDefinition>>) -> Self {
1550        self.tools([tool])
1551    }
1552
1553    /// Add tools in order.
1554    pub fn tools(
1555        mut self,
1556        tools: impl IntoIterator<Item = impl Into<Arc<ToolDefinition>>>,
1557    ) -> Self {
1558        let first = self.tools.is_empty();
1559        self.tools.extend(tools.into_iter().map(Into::into));
1560        self.warn_if_shadowed("tools", first && !self.tools.is_empty());
1561        self
1562    }
1563
1564    /// Add a provider-hosted tool: appended to `additional_params.tools`,
1565    /// so a later [`Self::additional_params`] with a `tools` key replaces
1566    /// it.
1567    pub fn provider_tool(self, tool: ProviderToolDefinition) -> Self {
1568        self.provider_tools(vec![tool])
1569    }
1570
1571    /// Add provider-hosted tools in order: appended to
1572    /// `additional_params.tools`.
1573    pub fn provider_tools(mut self, tools: Vec<ProviderToolDefinition>) -> Self {
1574        self.additional_params =
1575            merge_provider_tools_into_additional_params(self.additional_params.take(), tools);
1576        self
1577    }
1578
1579    /// Merge provider-specific parameters into the request's, key by key;
1580    /// `None` clears them, provider tools included. Provider conversion
1581    /// determines precedence over typed fields, and a key that overrides a
1582    /// typed field this request sets is logged.
1583    pub fn additional_params(
1584        mut self,
1585        additional_params: impl Into<Option<serde_json::Value>>,
1586    ) -> Self {
1587        let additional_params = additional_params.into();
1588        for key in shadowed_typed_fields(
1589            additional_params.as_ref(),
1590            &[
1591                ("temperature", self.temperature.is_some()),
1592                ("max_tokens", self.max_tokens.is_some()),
1593                ("tool_choice", self.tool_choice.is_some()),
1594                ("model", self.model.is_some()),
1595                ("tools", !self.tools.is_empty()),
1596                ("response_format", self.output_schema.is_some()),
1597            ],
1598        ) {
1599            warn_shadowed(key);
1600        }
1601        self.additional_params =
1602            json_utils::merge_params(self.additional_params.take(), additional_params);
1603        self
1604    }
1605
1606    /// Set, or with `None` clear, the temperature.
1607    pub fn temperature(mut self, temperature: impl Into<Option<f64>>) -> Self {
1608        self.temperature = temperature.into();
1609        self.warn_if_shadowed("temperature", self.temperature.is_some());
1610        self
1611    }
1612
1613    /// Set, or with `None` clear, the output-token limit. Provider-specific
1614    /// defaults and requirements apply.
1615    pub fn max_tokens(mut self, max_tokens: impl Into<Option<u64>>) -> Self {
1616        self.max_tokens = max_tokens.into();
1617        self.warn_if_shadowed("max_tokens", self.max_tokens.is_some());
1618        self
1619    }
1620
1621    /// Set the tool-selection policy.
1622    pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
1623        self.tool_choice = Some(tool_choice);
1624        self.warn_if_shadowed("tool_choice", true);
1625        self
1626    }
1627
1628    /// Set, or with `None` clear, a native structured-output schema for
1629    /// providers that support one. The returned content is not
1630    /// deserialized.
1631    pub fn output_schema(mut self, schema: impl Into<Option<schemars::Schema>>) -> Self {
1632        self.output_schema = schema.into();
1633        self.warn_if_shadowed("response_format", self.output_schema.is_some());
1634        self
1635    }
1636
1637    /// Opt in to sensitive content telemetry, off by default. See
1638    /// [`Self::record_telemetry_content`] for what that exposes.
1639    pub fn record_content_telemetry(mut self, enabled: bool) -> Self {
1640        self.record_telemetry_content = enabled;
1641        self
1642    }
1643
1644    /// Accept, or with `false` refuse, finish reasons outside the normalized
1645    /// vocabulary as a normal stop. See
1646    /// [`Self::accept_unknown_finish_reasons`] for what that lets through.
1647    ///
1648    /// [`Self::accept_unknown_finish_reasons`]: field@Self::accept_unknown_finish_reasons
1649    pub fn accept_unknown_finish_reasons(mut self, accept: bool) -> Self {
1650        self.accept_unknown_finish_reasons = accept;
1651        self
1652    }
1653
1654    /// Replace the portable generation options with `options`, a reusable
1655    /// value. Calls apply in order: this replaces every field, so a
1656    /// shortcut such as [`Self::seed`] called before it is lost, and one
1657    /// called after it sets its one field on top.
1658    ///
1659    /// ```
1660    /// use rig_core::completion::{CompletionRequest, Effort, GenerationOptions};
1661    ///
1662    /// let shared = GenerationOptions::new().reasoning(Effort::High).seed(1);
1663    /// let request = CompletionRequest::new("hi").seed(7).options(shared.clone()).seed(2);
1664    /// assert_eq!(request.options, shared.seed(2));
1665    /// ```
1666    pub fn options(mut self, options: GenerationOptions) -> Self {
1667        self.options = options;
1668        self
1669    }
1670
1671    /// Set the reasoning level or budget in [`Self::options`](field@Self::options), as
1672    /// [`GenerationOptions::reasoning`](GenerationOptions::reasoning) does,
1673    /// keeping its other fields. See
1674    /// [`Self::options`](method@Self::options) for the order of calls.
1675    pub fn reasoning(mut self, reasoning: impl Into<Reasoning>) -> Self {
1676        self.options = std::mem::take(&mut self.options).reasoning(reasoning);
1677        self
1678    }
1679
1680    /// Set the cache retention in [`Self::options`](field@Self::options), as
1681    /// [`GenerationOptions::cache`](GenerationOptions::cache) does,
1682    /// keeping its other fields. See
1683    /// [`Self::options`](method@Self::options) for the order of calls.
1684    pub fn cache(mut self, cache: CacheRetention) -> Self {
1685        self.options = std::mem::take(&mut self.options).cache(cache);
1686        self
1687    }
1688
1689    /// Set the service tier in [`Self::options`](field@Self::options), as
1690    /// [`GenerationOptions::service_tier`](GenerationOptions::service_tier) does,
1691    /// keeping its other fields. See
1692    /// [`Self::options`](method@Self::options) for the order of calls.
1693    pub fn service_tier(mut self, tier: ServiceTier) -> Self {
1694        self.options = std::mem::take(&mut self.options).service_tier(tier);
1695        self
1696    }
1697
1698    /// Set the answer verbosity in [`Self::options`](field@Self::options), as
1699    /// [`GenerationOptions::verbosity`](GenerationOptions::verbosity) does,
1700    /// keeping its other fields. See
1701    /// [`Self::options`](method@Self::options) for the order of calls.
1702    pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
1703        self.options = std::mem::take(&mut self.options).verbosity(verbosity);
1704        self
1705    }
1706
1707    /// Set whether the model may call several tools in one turn in
1708    /// [`Self::options`](field@Self::options), as
1709    /// [`GenerationOptions::parallel_tool_calls`] does, keeping its other
1710    /// fields. See
1711    /// [`Self::options`](method@Self::options) for the order of calls.
1712    pub fn parallel_tool_calls(mut self, parallel: bool) -> Self {
1713        self.options = std::mem::take(&mut self.options).parallel_tool_calls(parallel);
1714        self
1715    }
1716
1717    /// Set the nucleus sampling probability mass in [`Self::options`](field@Self::options), as
1718    /// [`GenerationOptions::top_p`](GenerationOptions::top_p) does,
1719    /// keeping its other fields. See
1720    /// [`Self::options`](method@Self::options) for the order of calls.
1721    pub fn top_p(mut self, top_p: f64) -> Self {
1722        self.options = std::mem::take(&mut self.options).top_p(top_p);
1723        self
1724    }
1725
1726    /// Set the sampling seed in [`Self::options`](field@Self::options), as
1727    /// [`GenerationOptions::seed`](GenerationOptions::seed) does,
1728    /// keeping its other fields. See
1729    /// [`Self::options`](method@Self::options) for the order of calls.
1730    pub fn seed(mut self, seed: u64) -> Self {
1731        self.options = std::mem::take(&mut self.options).seed(seed);
1732        self
1733    }
1734
1735    /// Set the stop sequences in [`Self::options`](field@Self::options), as
1736    /// [`GenerationOptions::stop`](GenerationOptions::stop) does,
1737    /// keeping its other fields. See
1738    /// [`Self::options`](method@Self::options) for the order of calls.
1739    pub fn stop<S: Into<String>>(mut self, stop: impl IntoIterator<Item = S>) -> Self {
1740        self.options = std::mem::take(&mut self.options).stop(stop);
1741        self
1742    }
1743
1744    /// Set what happens to an option the wire or model cannot honour in
1745    /// [`Self::options`](field@Self::options), as
1746    /// [`GenerationOptions::on_unsupported`] does, keeping its other fields. See
1747    /// [`Self::options`](method@Self::options) for the order of calls.
1748    pub fn on_unsupported(mut self, policy: OnUnsupported) -> Self {
1749        self.options = std::mem::take(&mut self.options).on_unsupported(policy);
1750        self
1751    }
1752
1753    /// Replace the typed per-provider options.
1754    ///
1755    /// Calls apply in order: this replaces every entry, so an entry set by
1756    /// an earlier [`Self::provider_option`] is lost, and a later
1757    /// [`Self::provider_option`] replaces its provider's entry on top.
1758    pub fn provider_options(mut self, options: ProviderOptions) -> Self {
1759        self.provider_options = options;
1760        self
1761    }
1762
1763    /// Store `options` as the entry of their provider
1764    /// ([`ExtensionOptions::Ext`]), replacing that provider's entry and
1765    /// keeping every other, as [`ProviderOptions::set`] does. Options that
1766    /// do not serialize fail the request's encode.
1767    ///
1768    /// The entry is always stored under `O::Ext`'s key, the built-in
1769    /// provider the options type belongs to. A third-party provider whose
1770    /// extension reuses a built-in options type (say `OpenAiOptions` for an
1771    /// OpenAI-compatible gateway) must store them with
1772    /// [`ProviderOptions::with::<P>`](ProviderOptions::with) instead, or its
1773    /// wire never reads them.
1774    ///
1775    /// ```
1776    /// use rig_core::completion::{CompletionRequest, ProviderOptions};
1777    /// use rig_core::providers::openrouter::extension::{
1778    ///     OpenRouterExt, OpenRouterOptions, ProviderPreferences,
1779    /// };
1780    ///
1781    /// let request = CompletionRequest::new("hi").provider_option(
1782    ///     OpenRouterOptions::new().provider(ProviderPreferences::new().allow_fallbacks(false)),
1783    /// );
1784    /// assert!(request.provider_options.contains::<OpenRouterExt>());
1785    /// ```
1786    pub fn provider_option<O: ExtensionOptions>(mut self, options: O) -> Self {
1787        self.provider_options = std::mem::take(&mut self.provider_options).set(options);
1788        self
1789    }
1790
1791    /// The input messages telemetry records: the conversation with the
1792    /// documents inserted after any leading system messages.
1793    pub fn messages_for_telemetry(&self) -> Vec<Arc<Message>> {
1794        let mut messages = self.chat_history.clone();
1795        if let Some(documents) = self.normalized_documents() {
1796            insert_after_leading_system(&mut messages, documents);
1797        }
1798        messages
1799    }
1800
1801    /// Log a typed field `key` that `additional_params` already overrides.
1802    fn warn_if_shadowed(&self, key: &'static str, set: bool) {
1803        if !shadowed_typed_fields(self.additional_params.as_ref(), &[(key, set)]).is_empty() {
1804            warn_shadowed(key);
1805        }
1806    }
1807}
1808
1809fn warn_shadowed(key: &str) {
1810    if matches!(key, "tools" | "response_format") {
1811        tracing::warn!(
1812            key,
1813            "additional_params also carries `{key}`; the provider decides how it combines with the typed field"
1814        );
1815    } else {
1816        tracing::warn!(
1817            key,
1818            "additional_params overrides the typed `{key}` field set on the same request"
1819        );
1820    }
1821}
1822
1823impl From<&str> for CompletionRequest {
1824    fn from(prompt: &str) -> Self {
1825        Self::new(prompt)
1826    }
1827}
1828
1829impl From<String> for CompletionRequest {
1830    fn from(prompt: String) -> Self {
1831        Self::new(prompt)
1832    }
1833}
1834
1835impl From<Message> for CompletionRequest {
1836    fn from(prompt: Message) -> Self {
1837        Self::new(prompt)
1838    }
1839}
1840
1841/// The conversation as given, ending with the prompt. An empty one fails
1842/// [`CompletionRequest::validate_message_content`].
1843impl From<Vec<Message>> for CompletionRequest {
1844    fn from(chat_history: Vec<Message>) -> Self {
1845        Self::conversation(chat_history.into_iter().map(Arc::new).collect())
1846    }
1847}
1848
1849/// The passthrough keys that will override a typed field the caller also set.
1850/// The override itself is the documented precedence (see
1851/// [`CompletionRequest::additional_params`]); naming the collisions
1852/// makes an accidental one visible instead of silent.
1853pub(crate) fn shadowed_typed_fields<'a>(
1854    additional_params: Option<&serde_json::Value>,
1855    typed: &[(&'a str, bool)],
1856) -> Vec<&'a str> {
1857    let Some(serde_json::Value::Object(params)) = additional_params else {
1858        return Vec::new();
1859    };
1860    typed
1861        .iter()
1862        .filter(|(key, set)| *set && params.contains_key(*key))
1863        .map(|(key, _)| *key)
1864        .collect()
1865}
1866
1867#[cfg(test)]
1868mod tests;
1869
1870#[cfg(test)]
1871mod response_identity_tests;
1872
1873#[cfg(test)]
1874mod plain_value_tests;