use std::{
fmt::{Debug, Formatter},
time::Duration,
};
use serde_json::Value;
use crate::error::{Error, ErrorKind, Result};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum ReasoningEffort {
None,
Minimal,
Low,
Medium,
High,
#[default]
XHigh,
Max,
}
impl ReasoningEffort {
pub const fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Minimal => "minimal",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::XHigh => "xhigh",
Self::Max => "max",
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ImageMediaType {
Png,
Jpeg,
Webp,
Gif,
}
impl ImageMediaType {
pub const fn mime_type(self) -> &'static str {
match self {
Self::Png => "image/png",
Self::Jpeg => "image/jpeg",
Self::Webp => "image/webp",
Self::Gif => "image/gif",
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct ImageInput {
media_type: ImageMediaType,
bytes: Vec<u8>,
}
impl ImageInput {
pub fn new(media_type: ImageMediaType, bytes: impl Into<Vec<u8>>) -> Result<Self> {
let bytes = bytes.into();
if bytes.is_empty() {
return Err(Error::new(
ErrorKind::InvalidInput,
"Codex image input must not be empty",
));
}
Ok(Self { media_type, bytes })
}
pub const fn media_type(&self) -> ImageMediaType {
self.media_type
}
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
}
impl Debug for ImageInput {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ImageInput")
.field("media_type", &self.media_type)
.field("byte_len", &self.bytes.len())
.finish()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ImageTurnRequest {
pub prompt: String,
pub model: String,
pub images: Vec<ImageInput>,
pub reasoning_effort: ReasoningEffort,
pub timeout: Duration,
}
impl ImageTurnRequest {
pub fn new(
prompt: impl Into<String>,
model: impl Into<String>,
images: Vec<ImageInput>,
) -> Self {
Self {
prompt: prompt.into(),
model: model.into(),
images,
reasoning_effort: ReasoningEffort::XHigh,
timeout: Duration::from_secs(10 * 60),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DynamicTool {
pub name: String,
pub description: String,
pub input_schema: Value,
}
impl DynamicTool {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
input_schema: Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
input_schema,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct AgentRequest {
pub input: String,
pub model: String,
pub reasoning_effort: ReasoningEffort,
pub previous_thread_id: Option<String>,
pub tools: Vec<DynamicTool>,
pub ephemeral: bool,
pub timeout: Duration,
}
impl AgentRequest {
pub fn new(input: impl Into<String>, model: impl Into<String>) -> Self {
Self {
input: input.into(),
model: model.into(),
reasoning_effort: ReasoningEffort::XHigh,
previous_thread_id: None,
tools: Vec::new(),
ephemeral: false,
timeout: Duration::from_secs(10 * 60),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DynamicToolCall {
pub call_id: String,
pub tool: String,
pub arguments: Value,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ToolResult {
pub success: bool,
pub text: String,
}
impl ToolResult {
pub fn success(text: impl Into<String>) -> Self {
Self {
success: true,
text: text.into(),
}
}
pub fn failure(text: impl Into<String>) -> Self {
Self {
success: false,
text: text.into(),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TokenUsage {
pub input_tokens: u64,
pub output_tokens: u64,
pub cached_input_tokens: u64,
pub reasoning_output_tokens: u64,
pub last_input_tokens: Option<u64>,
pub last_output_tokens: Option<u64>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CompletedTurn {
pub thread_id: String,
pub turn_id: String,
pub answer: String,
pub usage: Option<TokenUsage>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum AgentEvent {
ProviderInput(String),
ToolCall(DynamicToolCall),
Completed(CompletedTurn),
}