Skip to main content

openai_types/chat/
manual.rs

1// Manual: hand-crafted chat completion types (request, response, streaming, tools).
2// These supplement the auto-generated _gen.rs types.
3
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7// Re-export shared types (canonical definitions in shared/common.rs)
8pub use crate::shared::{
9    CompletionTokensDetails, FinishReason, PromptTokensDetails, ReasoningEffort, Role,
10    SearchContextSize, ServiceTier, Usage,
11};
12
13// ── Request types ──
14
15/// Request body for `POST /chat/completions`.
16#[derive(Debug, Clone, Serialize)]
17#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
18pub struct ChatCompletionRequest {
19    /// Model ID, e.g. "gpt-4o", "gpt-4o-mini".
20    pub model: String,
21
22    /// Messages in the conversation.
23    pub messages: Vec<ChatCompletionMessageParam>,
24
25    /// Penalty for frequent tokens. Range: -2.0 to 2.0.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub frequency_penalty: Option<f64>,
28
29    /// Modify likelihood of specific tokens. Maps token ID -> bias (-100 to 100).
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub logit_bias: Option<HashMap<String, i32>>,
32
33    /// Whether to return log probabilities.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub logprobs: Option<bool>,
36
37    /// Number of most likely tokens to return at each position (0-20).
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub top_logprobs: Option<u8>,
40
41    /// Upper bound on tokens to generate.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub max_completion_tokens: Option<i64>,
44
45    /// Number of completions to generate.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub n: Option<i32>,
48
49    /// Penalty for new tokens based on presence. Range: -2.0 to 2.0.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub presence_penalty: Option<f64>,
52
53    /// Response format constraint.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub response_format: Option<ResponseFormat>,
56
57    /// Seed for deterministic sampling.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub seed: Option<i64>,
60
61    /// Service tier.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub service_tier: Option<ServiceTier>,
64
65    /// Up to 4 stop sequences.
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub stop: Option<Stop>,
68
69    /// Whether to stream the response.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub stream: Option<bool>,
72
73    /// Stream options (e.g., include_usage).
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub stream_options: Option<StreamOptions>,
76
77    /// Sampling temperature (0-2).
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub temperature: Option<f64>,
80
81    /// Nucleus sampling parameter.
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub top_p: Option<f64>,
84
85    /// Tools available to the model.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub tools: Option<Vec<Tool>>,
88
89    /// How the model selects tools.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub tool_choice: Option<ToolChoice>,
92
93    /// Whether to enable parallel tool calls.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub parallel_tool_calls: Option<bool>,
96
97    /// End user identifier.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub user: Option<String>,
100
101    /// Whether to store for evals/distillation.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub store: Option<bool>,
104
105    /// Metadata key-value pairs.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub metadata: Option<HashMap<String, String>>,
108
109    /// Output modalities: ["text"] or ["text", "audio"].
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub modalities: Option<Vec<String>>,
112
113    /// Reasoning effort for reasoning models (o-series).
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub reasoning_effort: Option<ReasoningEffort>,
116
117    /// Response verbosity level.
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub verbosity: Option<String>,
120
121    /// Audio output parameters.
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub audio: Option<ChatCompletionAudioParam>,
124
125    /// Predicted output content (for Predicted Outputs feature).
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub prediction: Option<PredictionContent>,
128
129    /// Web search options.
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub web_search_options: Option<WebSearchOptions>,
132
133    /// Stable key for prompt caching.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub prompt_cache_key: Option<String>,
136
137    /// OpenRouter/Anthropic prompt caching control.
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub cache_control: Option<serde_json::Value>,
140
141    /// OpenRouter provider routing preferences.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub provider: Option<serde_json::Value>,
144
145    /// OpenRouter session ID for sticky routing (cache affinity).
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub session_id: Option<String>,
148
149    /// DEPRECATED: Maximum number of tokens to generate. Use max_completion_tokens instead.
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub max_tokens: Option<i64>,
152
153    /// DEPRECATED: A list of functions the model may call. Use tools instead.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub functions: Option<Vec<FunctionDef>>,
156
157    /// DEPRECATED: Controls how the model calls functions. Use tool_choice instead.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub function_call: Option<FunctionCallOption>,
160}
161
162impl ChatCompletionRequest {
163    /// Create a new request with the given model and messages.
164    pub fn new(model: impl Into<String>, messages: Vec<ChatCompletionMessageParam>) -> Self {
165        Self {
166            model: model.into(),
167            messages,
168            frequency_penalty: None,
169            logit_bias: None,
170            logprobs: None,
171            top_logprobs: None,
172            max_completion_tokens: None,
173            n: None,
174            presence_penalty: None,
175            response_format: None,
176            seed: None,
177            service_tier: None,
178            stop: None,
179            stream: None,
180            stream_options: None,
181            temperature: None,
182            top_p: None,
183            tools: None,
184            tool_choice: None,
185            parallel_tool_calls: None,
186            user: None,
187            store: None,
188            metadata: None,
189            modalities: None,
190            reasoning_effort: None,
191            verbosity: None,
192            audio: None,
193            prediction: None,
194            web_search_options: None,
195            prompt_cache_key: None,
196            cache_control: None,
197            provider: None,
198            session_id: None,
199            max_tokens: None,
200            functions: None,
201            function_call: None,
202        }
203    }
204
205    /// Set the model.
206    pub fn model(mut self, model: impl Into<String>) -> Self {
207        self.model = model.into();
208        self
209    }
210
211    /// Set the messages.
212    pub fn messages(mut self, messages: Vec<ChatCompletionMessageParam>) -> Self {
213        self.messages = messages;
214        self
215    }
216
217    /// Set the temperature (0-2).
218    pub fn temperature(mut self, temperature: f64) -> Self {
219        self.temperature = Some(temperature);
220        self
221    }
222
223    /// Set max completion tokens.
224    pub fn max_completion_tokens(mut self, max: i64) -> Self {
225        self.max_completion_tokens = Some(max);
226        self
227    }
228
229    /// Set the tools.
230    pub fn tools(mut self, tools: Vec<Tool>) -> Self {
231        self.tools = Some(tools);
232        self
233    }
234
235    /// Set the tool choice.
236    pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
237        self.tool_choice = Some(choice);
238        self
239    }
240
241    /// Set the response format.
242    pub fn response_format(mut self, format: ResponseFormat) -> Self {
243        self.response_format = Some(format);
244        self
245    }
246
247    /// Set reasoning effort for o-series models.
248    pub fn reasoning_effort(mut self, effort: ReasoningEffort) -> Self {
249        self.reasoning_effort = Some(effort);
250        self
251    }
252
253    /// Set prediction content for Predicted Outputs.
254    pub fn prediction(mut self, prediction: PredictionContent) -> Self {
255        self.prediction = Some(prediction);
256        self
257    }
258
259    /// Set top_p (nucleus sampling).
260    pub fn top_p(mut self, top_p: f64) -> Self {
261        self.top_p = Some(top_p);
262        self
263    }
264
265    /// Set seed for deterministic sampling.
266    pub fn seed(mut self, seed: i64) -> Self {
267        self.seed = Some(seed);
268        self
269    }
270
271    /// Set stop sequences.
272    pub fn stop(mut self, stop: Stop) -> Self {
273        self.stop = Some(stop);
274        self
275    }
276
277    /// Set user identifier.
278    pub fn user(mut self, user: impl Into<String>) -> Self {
279        self.user = Some(user.into());
280        self
281    }
282
283    /// Enable storage for evals/distillation.
284    pub fn store(mut self, store: bool) -> Self {
285        self.store = Some(store);
286        self
287    }
288
289    /// Set number of completions.
290    pub fn n(mut self, n: i32) -> Self {
291        self.n = Some(n);
292        self
293    }
294}
295
296/// Stop sequences: either a single string or up to 4 strings.
297#[derive(Debug, Clone, Serialize, Deserialize)]
298#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
299#[serde(untagged)]
300#[non_exhaustive]
301pub enum Stop {
302    Single(String),
303    Multiple(Vec<String>),
304}
305
306/// Stream options for chat completions.
307#[derive(Debug, Clone, Serialize, Deserialize)]
308#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
309pub struct StreamOptions {
310    /// If true, stream includes a final chunk with usage stats.
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub include_usage: Option<bool>,
313}
314
315/// Audio output parameters for chat completions.
316#[derive(Debug, Clone, Serialize, Deserialize)]
317#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
318pub struct ChatCompletionAudioParam {
319    /// Audio output format.
320    pub format: String,
321    /// Voice to use for audio output.
322    pub voice: String,
323}
324
325/// Predicted output content for the Predicted Outputs feature.
326#[derive(Debug, Clone, Serialize, Deserialize)]
327#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
328pub struct PredictionContent {
329    /// Always "content".
330    #[serde(rename = "type")]
331    pub type_: String,
332    /// The predicted content.
333    pub content: serde_json::Value,
334}
335
336/// Web search options.
337#[derive(Debug, Clone, Serialize, Deserialize)]
338#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
339pub struct WebSearchOptions {
340    /// Search context size.
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub search_context_size: Option<SearchContextSize>,
343    /// User location for search relevance.
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub user_location: Option<WebSearchUserLocation>,
346}
347
348/// User location for web search.
349#[derive(Debug, Clone, Serialize, Deserialize)]
350#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
351pub struct WebSearchUserLocation {
352    /// Always "approximate".
353    #[serde(rename = "type")]
354    pub type_: String,
355    /// Approximate location details.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub approximate: Option<ApproximateLocation>,
358}
359
360/// Approximate user location.
361#[derive(Debug, Clone, Serialize, Deserialize)]
362#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
363pub struct ApproximateLocation {
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub city: Option<String>,
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub country: Option<String>,
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub region: Option<String>,
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub timezone: Option<String>,
372}
373
374/// Response format constraint.
375#[derive(Debug, Clone, Serialize, Deserialize)]
376#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
377#[serde(tag = "type")]
378#[non_exhaustive]
379pub enum ResponseFormat {
380    #[serde(rename = "text")]
381    Text,
382    #[serde(rename = "json_object")]
383    JsonObject,
384    #[serde(rename = "json_schema")]
385    JsonSchema { json_schema: JsonSchema },
386}
387
388/// JSON Schema for structured output.
389#[derive(Debug, Clone, Serialize, Deserialize)]
390#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
391pub struct JsonSchema {
392    pub name: String,
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub description: Option<String>,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub schema: Option<serde_json::Value>,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub strict: Option<bool>,
399}
400
401// ── Message types (input) ──
402
403/// A message in the conversation (request side).
404#[derive(Debug, Clone, Serialize, Deserialize)]
405#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
406#[serde(tag = "role")]
407#[non_exhaustive]
408pub enum ChatCompletionMessageParam {
409    #[serde(rename = "system")]
410    System {
411        content: String,
412        #[serde(skip_serializing_if = "Option::is_none")]
413        name: Option<String>,
414    },
415    #[serde(rename = "developer")]
416    Developer {
417        content: String,
418        #[serde(skip_serializing_if = "Option::is_none")]
419        name: Option<String>,
420    },
421    #[serde(rename = "user")]
422    User {
423        content: UserContent,
424        #[serde(skip_serializing_if = "Option::is_none")]
425        name: Option<String>,
426    },
427    #[serde(rename = "assistant")]
428    Assistant {
429        #[serde(skip_serializing_if = "Option::is_none")]
430        content: Option<String>,
431        #[serde(skip_serializing_if = "Option::is_none")]
432        name: Option<String>,
433        #[serde(skip_serializing_if = "Option::is_none")]
434        tool_calls: Option<Vec<ToolCall>>,
435        #[serde(skip_serializing_if = "Option::is_none")]
436        refusal: Option<String>,
437    },
438    #[serde(rename = "tool")]
439    Tool {
440        content: String,
441        tool_call_id: String,
442    },
443}
444
445/// User message content: either a plain string or a list of content parts.
446#[derive(Debug, Clone, Serialize, Deserialize)]
447#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
448#[serde(untagged)]
449#[non_exhaustive]
450pub enum UserContent {
451    Text(String),
452    Parts(Vec<ContentPart>),
453}
454
455/// A content part in a multi-part user message.
456#[derive(Debug, Clone, Serialize, Deserialize)]
457#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
458#[serde(tag = "type")]
459#[non_exhaustive]
460pub enum ContentPart {
461    #[serde(rename = "text")]
462    Text { text: String },
463    #[serde(rename = "image_url")]
464    ImageUrl { image_url: ImageUrl },
465    #[serde(rename = "input_audio")]
466    InputAudio { input_audio: InputAudio },
467}
468
469/// Image detail level for vision.
470#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
471#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
472#[serde(rename_all = "snake_case")]
473#[non_exhaustive]
474pub enum ImageDetail {
475    Auto,
476    Low,
477    High,
478}
479
480/// Image URL reference in a content part.
481#[derive(Debug, Clone, Serialize, Deserialize)]
482#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
483pub struct ImageUrl {
484    pub url: String,
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub detail: Option<ImageDetail>,
487}
488
489/// Audio input in a content part.
490#[derive(Debug, Clone, Serialize, Deserialize)]
491#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
492pub struct InputAudio {
493    pub data: String,
494    pub format: String,
495}
496
497// ── Tool / function calling types ──
498
499/// A tool available to the model.
500#[derive(Debug, Clone, Serialize, Deserialize)]
501#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
502pub struct Tool {
503    #[serde(rename = "type")]
504    pub type_: String,
505    pub function: FunctionDef,
506}
507
508/// Recursively strip `format` and `minimum:0` from JSON schema values.
509/// schemars adds int32/int64/uint32/double format fields that some providers reject.
510fn strip_format_recursive(value: &mut serde_json::Value) {
511    if let Some(map) = value.as_object_mut() {
512        map.remove("format");
513        if map.get("minimum") == Some(&serde_json::json!(0.0)) {
514            map.remove("minimum");
515        }
516        for v in map.values_mut() {
517            strip_format_recursive(v);
518        }
519    } else if let Some(arr) = value.as_array_mut() {
520        for v in arr {
521            strip_format_recursive(v);
522        }
523    }
524}
525
526impl Tool {
527    /// Create a standard function tool.
528    /// Strips `format` and `minimum:0` fields recursively for cross-provider compatibility
529    /// (schemars adds int32/int64/uint32/double which providers like Cerebras reject).
530    pub fn function(
531        name: impl Into<String>,
532        description: impl Into<String>,
533        mut parameters: serde_json::Value,
534    ) -> Self {
535        strip_format_recursive(&mut parameters);
536        Self {
537            type_: "function".to_string(),
538            function: FunctionDef {
539                name: name.into(),
540                description: Some(description.into()),
541                parameters: Some(parameters),
542                strict: Some(true),
543            },
544        }
545    }
546
547    /// Web search tool (used by gpt-4o-search models)
548    pub fn web_search() -> Self {
549        Self {
550            type_: "web_search".to_string(),
551            function: FunctionDef {
552                name: "".to_string(),
553                description: None,
554                parameters: None,
555                strict: None,
556            },
557        }
558    }
559
560    /// File search tool
561    pub fn file_search() -> Self {
562        Self {
563            type_: "file_search".to_string(),
564            function: FunctionDef {
565                name: "".to_string(),
566                description: None,
567                parameters: None,
568                strict: None,
569            },
570        }
571    }
572
573    /// Code interpreter tool
574    pub fn code_interpreter() -> Self {
575        Self {
576            type_: "code_interpreter".to_string(),
577            function: FunctionDef {
578                name: "".to_string(),
579                description: None,
580                parameters: None,
581                strict: None,
582            },
583        }
584    }
585}
586
587/// Function definition within a tool.
588#[derive(Debug, Clone, Serialize, Deserialize)]
589#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
590pub struct FunctionDef {
591    pub name: String,
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub description: Option<String>,
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub parameters: Option<serde_json::Value>,
596    #[serde(skip_serializing_if = "Option::is_none")]
597    pub strict: Option<bool>,
598}
599
600/// DEPRECATED: How the model calls functions (use ToolChoice instead).
601#[derive(Debug, Clone, Serialize, Deserialize)]
602#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
603#[serde(untagged)]
604#[non_exhaustive]
605pub enum FunctionCallOption {
606    /// "none" or "auto".
607    Mode(String),
608    /// Force a specific function.
609    Named { name: String },
610}
611
612/// How the model picks tools.
613#[derive(Debug, Clone, Serialize, Deserialize)]
614#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
615#[serde(untagged)]
616#[non_exhaustive]
617pub enum ToolChoice {
618    /// "none", "auto", or "required"
619    Mode(String),
620    /// Force a specific function.
621    Named {
622        #[serde(rename = "type")]
623        type_: String,
624        function: ToolChoiceFunction,
625    },
626}
627
628/// Specifies which function to call.
629#[derive(Debug, Clone, Serialize, Deserialize)]
630#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
631pub struct ToolChoiceFunction {
632    pub name: String,
633}
634
635/// A tool call made by the assistant.
636#[derive(Debug, Clone, Serialize, Deserialize)]
637#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
638pub struct ToolCall {
639    pub id: String,
640    #[serde(rename = "type")]
641    pub type_: String,
642    pub function: FunctionCall,
643}
644
645/// A function call within a tool call (name + JSON arguments).
646#[derive(Debug, Clone, Serialize, Deserialize)]
647#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
648pub struct FunctionCall {
649    pub name: String,
650    pub arguments: String,
651}
652
653// ── Response types ──
654
655/// Response from `POST /chat/completions`.
656#[derive(Debug, Clone, Deserialize)]
657#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
658pub struct ChatCompletionResponse {
659    pub id: String,
660    pub choices: Vec<ChatCompletionChoice>,
661    pub created: i64,
662    pub model: String,
663    pub object: String,
664    #[serde(default)]
665    pub service_tier: Option<ServiceTier>,
666    #[serde(default)]
667    pub system_fingerprint: Option<String>,
668    #[serde(default)]
669    pub usage: Option<Usage>,
670    /// Session ID echoed back (OpenRouter sticky routing).
671    #[serde(default)]
672    pub session_id: Option<String>,
673}
674
675/// A single choice in a chat completion response.
676#[derive(Debug, Clone, Deserialize)]
677#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
678pub struct ChatCompletionChoice {
679    pub finish_reason: FinishReason,
680    pub index: i32,
681    pub message: ChatCompletionMessage,
682    #[serde(default)]
683    pub logprobs: Option<ChoiceLogprobs>,
684}
685
686/// The assistant's message in a response.
687#[derive(Debug, Clone, Deserialize)]
688#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
689pub struct ChatCompletionMessage {
690    pub role: Role,
691    #[serde(default)]
692    pub content: Option<String>,
693    #[serde(default)]
694    pub refusal: Option<String>,
695    #[serde(default)]
696    pub tool_calls: Option<Vec<ToolCall>>,
697    #[serde(default)]
698    pub annotations: Option<Vec<Annotation>>,
699}
700
701/// Log probability information.
702#[derive(Debug, Clone, Deserialize)]
703#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
704pub struct ChoiceLogprobs {
705    #[serde(default)]
706    pub content: Option<Vec<TokenLogprob>>,
707    #[serde(default)]
708    pub refusal: Option<Vec<TokenLogprob>>,
709}
710
711/// Log probability for a single token.
712#[derive(Debug, Clone, Deserialize)]
713#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
714pub struct TokenLogprob {
715    pub token: String,
716    pub logprob: f64,
717    #[serde(default)]
718    pub bytes: Option<Vec<u8>>,
719    #[serde(default)]
720    pub top_logprobs: Option<Vec<TopLogprob>>,
721}
722
723/// Top logprob candidate.
724#[derive(Debug, Clone, Serialize, Deserialize)]
725#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
726pub struct TopLogprob {
727    pub token: String,
728    pub logprob: f64,
729    #[serde(default)]
730    pub bytes: Option<Vec<u8>>,
731}
732
733/// URL citation annotation.
734#[derive(Debug, Clone, Serialize, Deserialize)]
735#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
736pub struct Annotation {
737    #[serde(rename = "type")]
738    pub type_: String,
739    #[serde(default)]
740    pub url_citation: Option<UrlCitation>,
741}
742
743/// URL citation details.
744#[derive(Debug, Clone, Serialize, Deserialize)]
745#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
746pub struct UrlCitation {
747    pub end_index: i32,
748    pub start_index: i32,
749    pub title: String,
750    pub url: String,
751}
752
753// ── Streaming types ──
754
755/// A chunk in a streaming chat completion response.
756#[derive(Debug, Clone, Deserialize)]
757#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
758pub struct ChatCompletionChunk {
759    pub id: String,
760    pub choices: Vec<ChunkChoice>,
761    pub created: i64,
762    pub model: String,
763    pub object: String,
764    #[serde(default)]
765    pub service_tier: Option<ServiceTier>,
766    #[serde(default)]
767    pub system_fingerprint: Option<String>,
768    #[serde(default)]
769    pub usage: Option<Usage>,
770    /// Session ID echoed back (OpenRouter sticky routing).
771    #[serde(default)]
772    pub session_id: Option<String>,
773}
774
775/// A choice within a streaming chunk.
776#[derive(Debug, Clone, Deserialize)]
777#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
778pub struct ChunkChoice {
779    pub delta: ChoiceDelta,
780    pub finish_reason: Option<FinishReason>,
781    pub index: i32,
782    #[serde(default)]
783    pub logprobs: Option<ChoiceLogprobs>,
784}
785
786/// Delta content in a streaming chunk.
787#[derive(Debug, Clone, Deserialize)]
788#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
789pub struct ChoiceDelta {
790    #[serde(default)]
791    pub content: Option<String>,
792    #[serde(default)]
793    pub role: Option<Role>,
794    #[serde(default)]
795    pub refusal: Option<String>,
796    #[serde(default)]
797    pub tool_calls: Option<Vec<DeltaToolCall>>,
798}
799
800/// A tool call delta in a streaming chunk.
801#[derive(Debug, Clone, Deserialize)]
802#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
803pub struct DeltaToolCall {
804    pub index: i32,
805    #[serde(default)]
806    pub id: Option<String>,
807    #[serde(default)]
808    pub function: Option<DeltaFunctionCall>,
809    #[serde(default, rename = "type")]
810    pub type_: Option<String>,
811}
812
813/// Function call delta in a streaming chunk.
814#[derive(Debug, Clone, Deserialize)]
815#[cfg_attr(feature = "structured", derive(schemars::JsonSchema))]
816pub struct DeltaFunctionCall {
817    #[serde(default)]
818    pub arguments: Option<String>,
819    #[serde(default)]
820    pub name: Option<String>,
821}