use serde::{Deserialize, Serialize};
use crate::completion::{self, CompletionError, NormalizeCompletionResponse};
use crate::providers::openai;
use crate::telemetry::ProviderResponseExt;
pub const GLM_4_7: &str = "zai-org-glm-4.7";
pub const GLM_5_2: &str = "zai-org-glm-5-2";
pub const QWEN3_5_9B: &str = "qwen3-5-9b";
pub const QWEN3_5_397B_A17B: &str = "qwen3-5-397b-a17b";
pub const QWEN3_235B_A22B_THINKING: &str = "qwen3-235b-a22b-thinking-2507";
pub const QWEN3_VL_235B_A22B: &str = "qwen3-vl-235b-a22b";
pub const QWEN3_CODER_480B: &str = "qwen3-coder-480b-a35b-instruct-turbo";
pub const VENICE_UNCENSORED_1_2: &str = "venice-uncensored-1-2";
pub const GEMINI_3_6_FLASH: &str = "gemini-3-6-flash";
pub const GROK_4_6: &str = "grok-4-6";
pub const MISTRAL_SMALL_2603: &str = "mistral-small-2603";
pub const MISTRAL_SMALL_3_2_24B: &str = "mistral-small-3-2-24b-instruct";
pub type CompletionModel<H = reqwest::Client> =
openai::completion::GenericCompletionModel<super::client::VeniceExt, H>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WebSearchMode {
Off,
On,
Auto,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct VeniceParameters {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub character_slug: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strip_thinking_response: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disable_thinking: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_web_search: Option<WebSearchMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_web_scraping: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_x_search: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_web_citations: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_search_results_in_stream: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub return_search_results_as_documents: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_venice_system_prompt: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prompt_cache_key: Option<String>,
}
impl VeniceParameters {
pub fn new() -> Self {
Self::default()
}
pub fn character_slug(mut self, slug: impl Into<String>) -> Self {
self.character_slug = Some(slug.into());
self
}
pub fn strip_thinking_response(mut self, strip: bool) -> Self {
self.strip_thinking_response = Some(strip);
self
}
pub fn disable_thinking(mut self, disable: bool) -> Self {
self.disable_thinking = Some(disable);
self
}
pub fn enable_web_search(mut self, mode: WebSearchMode) -> Self {
self.enable_web_search = Some(mode);
self
}
pub fn enable_web_scraping(mut self, enable: bool) -> Self {
self.enable_web_scraping = Some(enable);
self
}
pub fn enable_x_search(mut self, enable: bool) -> Self {
self.enable_x_search = Some(enable);
self
}
pub fn enable_web_citations(mut self, enable: bool) -> Self {
self.enable_web_citations = Some(enable);
self
}
pub fn include_search_results_in_stream(mut self, include: bool) -> Self {
self.include_search_results_in_stream = Some(include);
self
}
pub fn return_search_results_as_documents(mut self, as_documents: bool) -> Self {
self.return_search_results_as_documents = Some(as_documents);
self
}
pub fn include_venice_system_prompt(mut self, include: bool) -> Self {
self.include_venice_system_prompt = Some(include);
self
}
pub fn prompt_cache_key(mut self, key: impl Into<String>) -> Self {
self.prompt_cache_key = Some(key.into());
self
}
pub fn into_additional_params(self) -> serde_json::Value {
serde_json::json!({ "venice_parameters": self })
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct WebSearchCitation {
#[serde(default)]
pub title: String,
#[serde(default)]
pub url: String,
#[serde(default)]
pub content: String,
#[serde(default)]
pub date: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct VeniceParametersEcho {
#[serde(flatten)]
pub parameters: VeniceParameters,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enable_e2ee: Option<bool>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub web_search_citations: Vec<WebSearchCitation>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct Cost {
#[serde(default)]
pub usd: f64,
#[serde(default)]
pub diem: f64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CompletionResponse {
#[serde(flatten)]
pub openai: openai::CompletionResponse,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub venice_parameters: Option<VeniceParametersEcho>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cost: Option<Cost>,
}
impl CompletionResponse {
pub fn web_search_citations(&self) -> &[WebSearchCitation] {
self.venice_parameters
.as_ref()
.map(|parameters| parameters.web_search_citations.as_slice())
.unwrap_or_default()
}
}
impl NormalizeCompletionResponse for CompletionResponse {
fn normalize(self, provider: &str) -> Result<completion::CompletionResponse, CompletionError> {
self.openai.normalize(provider)
}
}
impl ProviderResponseExt for CompletionResponse {
type Usage = <openai::CompletionResponse as ProviderResponseExt>::Usage;
fn get_response_id(&self) -> Option<String> {
self.openai.get_response_id()
}
fn get_response_model_name(&self) -> Option<String> {
self.openai.get_response_model_name()
}
fn get_text_response(&self) -> Option<String> {
self.openai.get_text_response()
}
fn get_usage(&self) -> Option<Self::Usage> {
self.openai.get_usage()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn venice_parameters_only_serialize_set_fields() {
let params = VeniceParameters::new()
.enable_web_search(WebSearchMode::Auto)
.disable_thinking(true);
let json = serde_json::to_value(¶ms).expect("parameters should serialize");
assert_eq!(
json,
serde_json::json!({
"enable_web_search": "auto",
"disable_thinking": true,
})
);
}
#[test]
fn venice_parameters_wrap_into_additional_params() {
let json = VeniceParameters::new()
.character_slug("venice")
.into_additional_params();
assert_eq!(
json,
serde_json::json!({ "venice_parameters": { "character_slug": "venice" } })
);
}
#[test]
fn completion_response_preserves_venice_blocks() {
let body = serde_json::json!({
"id": "chatcmpl-1",
"object": "chat.completion",
"created": 0,
"model": "qwen3-5-9b",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "hi"},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
"cost": {"usd": 0.000_002_65, "diem": 0.0},
"venice_parameters": {
"enable_web_search": "on",
"enable_e2ee": true,
"web_search_citations": [{
"title": "Rust",
"url": "https://example.com",
"content": "text",
"date": ""
}]
}
});
let response: CompletionResponse =
serde_json::from_value(body).expect("response should decode");
assert_eq!(response.openai.id, "chatcmpl-1");
assert_eq!(response.get_text_response().as_deref(), Some("hi"));
assert_eq!(response.cost.expect("cost").diem, 0.0);
assert_eq!(response.web_search_citations().len(), 1);
assert_eq!(response.web_search_citations()[0].title, "Rust");
assert_eq!(
response
.venice_parameters
.expect("venice parameters")
.parameters
.enable_web_search,
Some(WebSearchMode::On)
);
}
}