use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ResponseFormat {
JsonObject,
Text,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ImageGeneration {
pub quality: Option<String>, pub size: Option<String>, pub output_format: Option<String>, }
#[derive(Serialize, Debug, Clone)]
pub struct ChatArguments {
pub model: String,
pub messages: Vec<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub n: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_generation: Option<ImageGeneration>,
#[serde(skip_serializing_if = "Option::is_none", rename = "server_tools")]
pub grok_tools: Option<Vec<GrokTool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<OpenAITool>>,
}
impl ChatArguments {
pub fn new(model: impl AsRef<str>, messages: Vec<Message>) -> ChatArguments {
ChatArguments {
model: model.as_ref().to_owned(),
messages,
temperature: None,
top_p: None,
n: None,
stream: None,
stop: None,
max_tokens: None,
presence_penalty: None,
frequency_penalty: None,
user: None,
response_format: None,
image_generation: None,
grok_tools: None,
tools: None,
}
}
pub fn with_grok_tools(mut self, tools: Vec<GrokTool>) -> Self {
self.grok_tools = Some(tools);
self
}
pub fn with_openai_tools(mut self, tools: Vec<OpenAITool>) -> Self {
self.tools = Some(tools);
self
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct ChatCompletion {
#[serde(default)]
pub id: Option<String>,
pub created: u32,
#[serde(default)]
pub model: Option<String>,
#[serde(default)]
pub object: Option<String>,
pub choices: Vec<Choice>,
pub usage: Usage,
}
impl std::fmt::Display for ChatCompletion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.choices[0].message.content)?;
Ok(())
}
}
pub mod stream {
use bytes::Bytes;
use futures_util::Stream;
use serde::Deserialize;
use std::pin::Pin;
use std::str;
use std::task::Poll;
#[derive(Deserialize, Debug, Clone)]
pub struct ChatCompletionChunk {
pub id: String,
pub created: u32,
pub model: String,
pub choices: Vec<Choice>,
pub system_fingerprint: Option<String>,
}
impl std::fmt::Display for ChatCompletionChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.choices[0].delta.content.as_ref().unwrap_or(&"".into())
)?;
Ok(())
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Choice {
pub delta: ChoiceDelta,
pub index: u32,
pub finish_reason: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ChoiceDelta {
pub content: Option<String>,
}
pub struct ChatCompletionChunkStream {
byte_stream: Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>>>>,
buf: String,
}
impl ChatCompletionChunkStream {
pub(crate) fn new(stream: Pin<Box<dyn Stream<Item = reqwest::Result<Bytes>>>>) -> Self {
Self {
byte_stream: stream,
buf: String::new(),
}
}
fn deserialize_buf(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Option<anyhow::Result<ChatCompletionChunk>> {
let bufclone = self.buf.clone();
let mut chunks = bufclone.split("\n\n").peekable();
let first = chunks.next();
let second = chunks.peek();
match first {
Some(first) => match first.strip_prefix("data: ") {
Some(chunk) => {
if !chunk.ends_with("}") {
None
} else {
if let Some(second) = second {
if second.ends_with("}") {
cx.waker().wake_by_ref();
}
}
self.get_mut().buf = chunks.collect::<Vec<_>>().join("\n\n");
Some(
serde_json::from_str::<ChatCompletionChunk>(chunk)
.map_err(|e| anyhow::anyhow!(e)),
)
}
}
None => None,
},
None => None,
}
}
}
impl Stream for ChatCompletionChunkStream {
type Item = anyhow::Result<ChatCompletionChunk>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
if let Some(chunk) = self.as_mut().deserialize_buf(cx) {
return Poll::Ready(Some(chunk));
}
match self.byte_stream.as_mut().poll_next(cx) {
Poll::Ready(bytes_option) => match bytes_option {
Some(bytes_result) => match bytes_result {
Ok(bytes) => {
let data = str::from_utf8(&bytes)?.to_owned();
self.buf = self.buf.clone() + &data;
match self.deserialize_buf(cx) {
Some(chunk) => Poll::Ready(Some(chunk)),
None => {
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
Err(e) => Poll::Ready(Some(Err(e.into()))),
},
None => Poll::Ready(None),
},
Poll::Pending => Poll::Pending,
}
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Choice {
#[serde(default)]
pub index: Option<u32>,
pub message: Message,
pub finish_reason: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Message {
pub role: String,
pub content: String,
}
pub enum Role {
System,
Assistant,
User,
}
#[derive(Serialize, Debug, Clone)]
pub struct GrokTool {
#[serde(rename = "type")]
pub tool_type: GrokToolType,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_domains: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub collection_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GrokToolType {
WebSearch,
XSearch,
CodeExecution,
CollectionsSearch,
Mcp,
}
impl GrokTool {
pub fn web_search() -> Self {
Self {
tool_type: GrokToolType::WebSearch,
allowed_domains: None,
from_date: None,
to_date: None,
collection_ids: None,
server_url: None,
}
}
pub fn x_search() -> Self {
Self {
tool_type: GrokToolType::XSearch,
allowed_domains: None,
from_date: None,
to_date: None,
collection_ids: None,
server_url: None,
}
}
pub fn code_execution() -> Self {
Self {
tool_type: GrokToolType::CodeExecution,
allowed_domains: None,
from_date: None,
to_date: None,
collection_ids: None,
server_url: None,
}
}
pub fn collections_search(collection_ids: Vec<String>) -> Self {
Self {
tool_type: GrokToolType::CollectionsSearch,
allowed_domains: None,
from_date: None,
to_date: None,
collection_ids: Some(collection_ids),
server_url: None,
}
}
pub fn mcp(server_url: String) -> Self {
Self {
tool_type: GrokToolType::Mcp,
allowed_domains: None,
from_date: None,
to_date: None,
collection_ids: None,
server_url: Some(server_url),
}
}
pub fn with_allowed_domains(mut self, domains: Vec<String>) -> Self {
self.allowed_domains = Some(domains);
self
}
pub fn with_date_range(mut self, from: impl Into<String>, to: impl Into<String>) -> Self {
self.from_date = Some(from.into());
self.to_date = Some(to.into());
self
}
}
#[derive(Serialize, Debug, Clone)]
pub struct ResponsesArguments {
pub model: String,
pub input: Vec<ResponsesMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<GrokTool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
}
impl ResponsesArguments {
pub fn new(model: impl AsRef<str>, input: Vec<ResponsesMessage>) -> Self {
Self {
model: model.as_ref().to_owned(),
input,
tools: None,
temperature: None,
max_output_tokens: None,
}
}
pub fn with_tools(mut self, tools: Vec<GrokTool>) -> Self {
self.tools = Some(tools);
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_max_output_tokens(mut self, max_tokens: u32) -> Self {
self.max_output_tokens = Some(max_tokens);
self
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResponsesMessage {
pub role: String,
pub content: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ResponsesCompletion {
#[serde(default)]
pub id: Option<String>,
pub output: Vec<ResponsesOutputItem>,
#[serde(default)]
pub citations: Vec<String>,
pub usage: ResponsesUsage,
}
impl ResponsesCompletion {
pub fn get_text_content(&self) -> String {
self.output
.iter()
.filter_map(|item| {
if item.item_type == "message" {
item.content.as_ref().map(|contents| {
contents
.iter()
.filter_map(|c| {
if c.content_type == "output_text" {
c.text.clone()
} else {
None
}
})
.collect::<Vec<_>>()
.join("")
})
} else {
None
}
})
.collect::<Vec<_>>()
.join("")
}
}
impl std::fmt::Display for ResponsesCompletion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.get_text_content())
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct ResponsesOutputItem {
#[serde(rename = "type")]
pub item_type: String,
#[serde(default)]
pub role: Option<String>,
#[serde(default)]
pub content: Option<Vec<ResponsesContent>>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ResponsesContent {
#[serde(rename = "type")]
pub content_type: String,
#[serde(default)]
pub text: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ResponsesUsage {
#[serde(default)]
pub input_tokens: u32,
#[serde(default)]
pub output_tokens: u32,
#[serde(default)]
pub total_tokens: u32,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum OpenAIToolType {
WebSearch,
FileSearch,
CodeInterpreter,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserLocation {
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
}
#[derive(Serialize, Debug, Clone)]
pub struct OpenAITool {
#[serde(rename = "type")]
pub tool_type: OpenAIToolType,
#[serde(skip_serializing_if = "Option::is_none")]
pub search_context_size: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_location: Option<UserLocation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_num_results: Option<u32>,
}
impl OpenAITool {
pub fn web_search() -> Self {
Self {
tool_type: OpenAIToolType::WebSearch,
search_context_size: None,
user_location: None,
max_num_results: None,
}
}
pub fn file_search() -> Self {
Self {
tool_type: OpenAIToolType::FileSearch,
search_context_size: None,
user_location: None,
max_num_results: None,
}
}
pub fn code_interpreter() -> Self {
Self {
tool_type: OpenAIToolType::CodeInterpreter,
search_context_size: None,
user_location: None,
max_num_results: None,
}
}
pub fn with_search_context_size(mut self, size: impl Into<String>) -> Self {
self.search_context_size = Some(size.into());
self
}
pub fn with_user_location(mut self, location: UserLocation) -> Self {
self.user_location = Some(location);
self
}
pub fn with_max_num_results(mut self, max_results: u32) -> Self {
self.max_num_results = Some(max_results);
self
}
}
#[derive(Serialize, Debug, Clone)]
pub struct OpenAIResponsesArguments {
pub model: String,
pub input: Vec<ResponsesMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<OpenAITool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
}
impl OpenAIResponsesArguments {
pub fn new(model: impl AsRef<str>, input: Vec<ResponsesMessage>) -> Self {
Self {
model: model.as_ref().to_owned(),
input,
tools: None,
temperature: None,
max_output_tokens: None,
}
}
pub fn with_tools(mut self, tools: Vec<OpenAITool>) -> Self {
self.tools = Some(tools);
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_max_output_tokens(mut self, max_tokens: u32) -> Self {
self.max_output_tokens = Some(max_tokens);
self
}
}