use serde::{Deserialize, Serialize};
use twox_hash::XxHash3_128;
use schemars::JsonSchema;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[schemars(rename = "agent.codex_sdk.AgentBase")]
pub struct AgentBase {
pub upstream: super::Upstream,
pub model: String,
pub output_mode: super::OutputMode,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub effort: Option<super::Effort>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub web_search_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub prefix_content: Option<super::super::completions::message::RichContent>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub suffix_content: Option<super::super::completions::message::RichContent>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub mcp_servers: Option<super::super::McpServers>,
}
impl AgentBase {
pub fn prepare(&mut self) {
self.effort = match self.effort.take() {
Some(effort) => effort.prepare(),
None => None,
};
self.web_search_enabled = match self.web_search_enabled {
Some(false) => None,
other => other,
};
self.prefix_content = match self.prefix_content.take() {
Some(prefix_content) if prefix_content.is_empty() => None,
Some(mut prefix_content) => {
prefix_content.prepare();
if prefix_content.is_empty() { None } else { Some(prefix_content) }
}
None => None,
};
self.suffix_content = match self.suffix_content.take() {
Some(suffix_content) if suffix_content.is_empty() => None,
Some(mut suffix_content) => {
suffix_content.prepare();
if suffix_content.is_empty() { None } else { Some(suffix_content) }
}
None => None,
};
self.mcp_servers = match self.mcp_servers.take() {
Some(mcp_servers) => super::super::mcp::mcp_servers::prepare(mcp_servers),
None => None,
};
}
pub fn validate(&self) -> Result<(), String> {
if self.model.is_empty() {
return Err("`model` string cannot be empty".to_string());
}
if let Some(effort) = &self.effort {
effort.validate()?;
}
if let Some(prefix_content) = &self.prefix_content {
prefix_content
.validate_text_or_image_only()
.map_err(|e| format!("`prefix_content`: {e}"))?;
}
if let Some(suffix_content) = &self.suffix_content {
suffix_content
.validate_text_or_image_only()
.map_err(|e| format!("`suffix_content`: {e}"))?;
}
if let Some(mcp_servers) = &self.mcp_servers {
super::super::mcp::mcp_servers::validate(mcp_servers)?;
}
Ok(())
}
pub fn merged_messages(
&self,
messages: Vec<super::super::completions::message::Message>,
) -> Vec<super::super::completions::message::Message> {
use super::super::completions::message::{Message, UserMessage};
let prefix_len = if self.prefix_content.is_some() { 1 } else { 0 };
let suffix_len = if self.suffix_content.is_some() { 1 } else { 0 };
let mut merged = Vec::with_capacity(prefix_len + messages.len() + suffix_len);
let mut prefix_inserted = self.prefix_content.is_none();
for msg in messages {
if !prefix_inserted {
if !matches!(msg, Message::System(_) | Message::Developer(_)) {
merged.push(Message::User(UserMessage {
content: self.prefix_content.clone().unwrap(),
name: None,
}));
prefix_inserted = true;
}
}
merged.push(msg);
}
if !prefix_inserted {
merged.push(Message::User(UserMessage {
content: self.prefix_content.clone().unwrap(),
name: None,
}));
}
if let Some(suffix_content) = &self.suffix_content {
merged.push(Message::User(UserMessage {
content: suffix_content.clone(),
name: None,
}));
}
merged
}
pub fn id(&self) -> String {
let mut hasher = XxHash3_128::with_seed(0);
hasher.write(serde_json::to_string(self).unwrap().as_bytes());
format!("{:0>22}", base62::encode(hasher.finish_128()))
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
#[schemars(rename = "agent.codex_sdk.Agent")]
pub struct Agent {
pub id: String,
#[serde(flatten)]
pub base: AgentBase,
}
impl TryFrom<AgentBase> for Agent {
type Error = String;
fn try_from(mut base: AgentBase) -> Result<Self, Self::Error> {
base.prepare();
base.validate()?;
let id = base.id();
Ok(Agent { id, base })
}
}