//! Serde structs modeling Venice's `/chat/completions`, `/audio/transcriptions`, //! `/audio/speech`, `/image/generate`, and `/image/edit` wire shapes. Request types are //! `Serialize`-only (we build them, Venice never sends them back); response types are //! `Deserialize`-only. Keeping the split means the untagged request content enum is never on a //! deserialize path, so a surprise response shape can't fail to match it. //! //! Field names match Venice's schema 1:1 (so the config's `model_id` becomes `model` here). Every //! request body is `additionalProperties: false`, so optional knobs carry `skip_serializing_if` //! to omit rather than send `null`. `/audio/speech` and `/image/edit` return raw binary (no //! response struct); only `/image/generate` returns JSON (`GenerateImageResponse`). use serde::{Deserialize, Serialize}; use super::config::VeniceParameters; #[derive(Debug, Serialize)] pub struct ChatCompletionRequest { pub model: String, pub messages: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub temperature: Option, #[serde(skip_serializing_if = "Option::is_none")] pub max_completion_tokens: Option, #[serde(skip_serializing_if = "Option::is_none")] pub top_p: Option, #[serde(skip_serializing_if = "Option::is_none")] pub frequency_penalty: Option, #[serde(skip_serializing_if = "Option::is_none")] pub presence_penalty: Option, #[serde(skip_serializing_if = "Option::is_none")] pub repetition_penalty: Option, #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_retention: Option, #[serde(skip_serializing_if = "Option::is_none")] pub venice_parameters: Option, } #[derive(Debug, Serialize)] pub struct ChatMessage { pub role: String, pub content: MessageContent, } /// A message body is either a bare string or a list of content parts. Venice accepts both; we /// send the parts form when a message carries an image or a file (baibot keeps text, images, and /// files in separate messages, so a parts list holds a single image part or file part). #[derive(Debug, Serialize)] #[serde(untagged)] pub enum MessageContent { Text(String), Parts(Vec), } #[derive(Debug, Serialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ContentPart { ImageUrl { image_url: ImageUrl }, File { file: FilePart }, } #[derive(Debug, Serialize)] pub struct ImageUrl { /// A `data:;base64,` URI for inline images. pub url: String, } #[derive(Debug, Serialize)] pub struct FilePart { /// A `data:;base64,` URI carrying the file bytes inline. pub file_data: String, #[serde(skip_serializing_if = "Option::is_none")] pub filename: Option, } /// Standard OpenAI-shaped chat completion response. We read `choices[0].message.content` and, /// when web search is on, the structured `venice_parameters.web_search_citations` (requested via /// `return_search_results_as_documents`) to rewrite the inline `^n^` superscripts into readable /// `[n]` references plus a `Sources:` block. `reasoning_content` carries the model's thinking when /// the model exposes it; it is appended only when `show_reasoning` is set. #[derive(Debug, Deserialize)] pub struct ChatCompletionResponse { pub choices: Vec, #[serde(default)] pub venice_parameters: Option, } #[derive(Debug, Deserialize)] pub struct ChatChoice { pub message: ResponseMessage, } #[derive(Debug, Deserialize)] pub struct ResponseMessage { #[serde(default)] pub content: Option, #[serde(default)] pub reasoning_content: Option, } /// The `venice_parameters` envelope on a chat-completion *response*, distinct from the request-side /// `VeniceParameters` bag. Only the citation list is read; other response-side fields are ignored. #[derive(Debug, Deserialize, Default)] pub struct ResponseVeniceParameters { #[serde(default)] pub web_search_citations: Vec, } /// Only the `title` and `url` are read (for rendering the `Sources:` block). Venice also returns /// `content` and `date` per citation; serde drops them, the same way the response structs above /// ignore the response fields baibot does not use. Both fields default to empty so a single /// citation that arrives without one (schema drift on scraped results) degrades gracefully in the /// rendered list instead of failing the whole response deserialization. #[derive(Debug, Deserialize)] pub struct WebSearchCitation { #[serde(default)] pub title: String, #[serde(default)] pub url: String, } /// `/audio/transcriptions` response. We read `text`; the optional `duration`/`timestamps` the /// API can return are not used in v1. #[derive(Debug, Deserialize)] pub struct TranscriptionResponse { pub text: String, } /// `/audio/speech` (`CreateSpeechRequestSchema`) request. `input` and `model` are always sent; /// the rest are omitted when unset. The response is raw binary audio, so there is no response /// struct. #[derive(Debug, Serialize)] pub struct SpeechRequest { pub model: String, pub input: String, #[serde(skip_serializing_if = "Option::is_none")] pub voice: Option, #[serde(skip_serializing_if = "Option::is_none")] pub speed: Option, #[serde(skip_serializing_if = "Option::is_none")] pub response_format: Option, #[serde(skip_serializing_if = "Option::is_none")] pub prompt: Option, #[serde(skip_serializing_if = "Option::is_none")] pub temperature: Option, #[serde(skip_serializing_if = "Option::is_none")] pub top_p: Option, } /// `/image/generate` (`GenerateImageRequest`) request. `return_binary` is pinned `false` and /// `variants` to `1` by the builder: baibot wants exactly one image returned as base64-in-JSON, /// which `GenerateImageResponse` then decodes. Flipping `return_binary` would make Venice answer /// with raw binary and break that JSON decode, so it is not configurable. #[derive(Debug, Serialize)] pub struct GenerateImageRequest { pub model: String, pub prompt: String, pub return_binary: bool, pub variants: u32, #[serde(skip_serializing_if = "Option::is_none")] pub negative_prompt: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cfg_scale: Option, #[serde(skip_serializing_if = "Option::is_none")] pub steps: Option, #[serde(skip_serializing_if = "Option::is_none")] pub style_preset: Option, #[serde(skip_serializing_if = "Option::is_none")] pub seed: Option, #[serde(skip_serializing_if = "Option::is_none")] pub safe_mode: Option, #[serde(skip_serializing_if = "Option::is_none")] pub hide_watermark: Option, #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, #[serde(skip_serializing_if = "Option::is_none")] pub width: Option, #[serde(skip_serializing_if = "Option::is_none")] pub height: Option, #[serde(skip_serializing_if = "Option::is_none")] pub aspect_ratio: Option, #[serde(skip_serializing_if = "Option::is_none")] pub resolution: Option, #[serde(skip_serializing_if = "Option::is_none")] pub quality: Option, #[serde(skip_serializing_if = "Option::is_none")] pub lora_strength: Option, #[serde(skip_serializing_if = "Option::is_none")] pub embed_exif_metadata: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enable_web_search: Option, } /// `/image/generate` response when `return_binary` is false: a JSON envelope carrying the images /// as base64 strings. We read `images[0]`; `request`/`timing` and other fields are ignored. `id` /// is telemetry only (logged, never used for correctness), so it is optional: a response that /// carries usable `images` must not fail to deserialize just because the telemetry field drifted. #[derive(Debug, Deserialize)] pub struct GenerateImageResponse { #[serde(default)] pub id: Option, pub images: Vec, } /// `/image/edit` (`EditImageRequest`) request. The source `image` is a base64-encoded string /// (Venice's `image` field is `anyOf` upload/base64/URL; we send base64-in-JSON, no multipart). /// The response is raw binary, so there is no response struct. #[derive(Debug, Serialize)] pub struct EditImageRequest { pub model: String, pub prompt: String, /// Base64-encoded source image bytes. pub image: String, #[serde(skip_serializing_if = "Option::is_none")] pub output_format: Option, #[serde(skip_serializing_if = "Option::is_none")] pub aspect_ratio: Option, #[serde(skip_serializing_if = "Option::is_none")] pub resolution: Option, #[serde(skip_serializing_if = "Option::is_none")] pub safe_mode: Option, }