objectiveai_sdk/agent/codex_sdk/
agent.rs1use serde::{Deserialize, Serialize};
4use twox_hash::XxHash3_128;
5use schemars::JsonSchema;
6
7#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
9#[schemars(rename = "agent.codex_sdk.AgentBase")]
10pub struct AgentBase {
11 pub upstream: super::Upstream,
13
14 pub model: String,
16
17 pub output_mode: super::OutputMode,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 #[schemars(extend("omitempty" = true))]
23 pub effort: Option<super::Effort>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 #[schemars(extend("omitempty" = true))]
28 pub web_search_enabled: Option<bool>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 #[schemars(extend("omitempty" = true))]
33 pub prefix_content: Option<super::super::completions::message::RichContent>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 #[schemars(extend("omitempty" = true))]
38 pub suffix_content: Option<super::super::completions::message::RichContent>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 #[schemars(extend("omitempty" = true))]
43 pub mcp_servers: Option<super::super::McpServers>,
44}
45
46impl AgentBase {
47 pub fn prepare(&mut self) {
49 self.effort = match self.effort.take() {
50 Some(effort) => effort.prepare(),
51 None => None,
52 };
53 self.web_search_enabled = match self.web_search_enabled {
54 Some(false) => None,
55 other => other,
56 };
57 self.prefix_content = match self.prefix_content.take() {
58 Some(prefix_content) if prefix_content.is_empty() => None,
59 Some(mut prefix_content) => {
60 prefix_content.prepare();
61 if prefix_content.is_empty() { None } else { Some(prefix_content) }
62 }
63 None => None,
64 };
65 self.suffix_content = match self.suffix_content.take() {
66 Some(suffix_content) if suffix_content.is_empty() => None,
67 Some(mut suffix_content) => {
68 suffix_content.prepare();
69 if suffix_content.is_empty() { None } else { Some(suffix_content) }
70 }
71 None => None,
72 };
73 self.mcp_servers = match self.mcp_servers.take() {
74 Some(mcp_servers) => super::super::mcp::mcp_servers::prepare(mcp_servers),
75 None => None,
76 };
77 }
78
79 pub fn validate(&self) -> Result<(), String> {
81 if self.model.is_empty() {
82 return Err("`model` string cannot be empty".to_string());
83 }
84 if let Some(effort) = &self.effort {
85 effort.validate()?;
86 }
87 if let Some(prefix_content) = &self.prefix_content {
88 prefix_content
89 .validate_text_or_image_only()
90 .map_err(|e| format!("`prefix_content`: {e}"))?;
91 }
92 if let Some(suffix_content) = &self.suffix_content {
93 suffix_content
94 .validate_text_or_image_only()
95 .map_err(|e| format!("`suffix_content`: {e}"))?;
96 }
97 if let Some(mcp_servers) = &self.mcp_servers {
98 super::super::mcp::mcp_servers::validate(mcp_servers)?;
99 }
100 Ok(())
101 }
102
103 pub fn merged_messages(
108 &self,
109 messages: Vec<super::super::completions::message::Message>,
110 ) -> Vec<super::super::completions::message::Message> {
111 use super::super::completions::message::{Message, UserMessage};
112 let prefix_len = if self.prefix_content.is_some() { 1 } else { 0 };
113 let suffix_len = if self.suffix_content.is_some() { 1 } else { 0 };
114 let mut merged = Vec::with_capacity(prefix_len + messages.len() + suffix_len);
115 let mut prefix_inserted = self.prefix_content.is_none();
116 for msg in messages {
117 if !prefix_inserted {
118 if !matches!(msg, Message::System(_) | Message::Developer(_)) {
119 merged.push(Message::User(UserMessage {
120 content: self.prefix_content.clone().unwrap(),
121 name: None,
122 }));
123 prefix_inserted = true;
124 }
125 }
126 merged.push(msg);
127 }
128 if !prefix_inserted {
129 merged.push(Message::User(UserMessage {
130 content: self.prefix_content.clone().unwrap(),
131 name: None,
132 }));
133 }
134 if let Some(suffix_content) = &self.suffix_content {
135 merged.push(Message::User(UserMessage {
136 content: suffix_content.clone(),
137 name: None,
138 }));
139 }
140 merged
141 }
142
143 pub fn id(&self) -> String {
145 let mut hasher = XxHash3_128::with_seed(0);
146 hasher.write(serde_json::to_string(self).unwrap().as_bytes());
147 format!("{:0>22}", base62::encode(hasher.finish_128()))
148 }
149}
150
151#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
153#[schemars(rename = "agent.codex_sdk.Agent")]
154pub struct Agent {
155 pub id: String,
157 #[serde(flatten)]
159 pub base: AgentBase,
160}
161
162impl TryFrom<AgentBase> for Agent {
163 type Error = String;
164 fn try_from(mut base: AgentBase) -> Result<Self, Self::Error> {
165 base.prepare();
166 base.validate()?;
167 let id = base.id();
168 Ok(Agent { id, base })
169 }
170}