objectiveai_sdk/agent/codex_sdk/
agent.rs1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use twox_hash::XxHash3_128;
6
7#[derive(
9 Clone,
10 Debug,
11 Default,
12 PartialEq,
13 Serialize,
14 Deserialize,
15 JsonSchema,
16 arbitrary::Arbitrary,
17)]
18#[schemars(rename = "agent.codex_sdk.AgentBase")]
19pub struct AgentBase {
20 pub upstream: super::Upstream,
22
23 pub model: String,
25
26 pub output_mode: super::OutputMode,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 #[schemars(extend("omitempty" = true))]
32 pub effort: Option<super::Effort>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 #[schemars(extend("omitempty" = true))]
37 pub web_search_enabled: Option<bool>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 #[schemars(extend("omitempty" = true))]
42 pub prefix_content: Option<super::super::completions::message::RichContent>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 #[schemars(extend("omitempty" = true))]
47 pub suffix_content: Option<super::super::completions::message::RichContent>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 #[schemars(extend("omitempty" = true))]
52 pub mcp_servers: Option<super::super::McpServers>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
59 #[schemars(extend("omitempty" = true))]
60 pub laboratories: Option<super::super::Laboratories>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
66 #[schemars(extend("omitempty" = true))]
67 pub client_objectiveai_mcp: Option<super::super::ClientObjectiveaiMcp>,
68}
69
70impl AgentBase {
71 pub fn prepare(&mut self) {
73 self.effort = match self.effort.take() {
74 Some(effort) => effort.prepare(),
75 None => None,
76 };
77 self.web_search_enabled = match self.web_search_enabled {
78 Some(false) => None,
79 other => other,
80 };
81 self.prefix_content = match self.prefix_content.take() {
82 Some(prefix_content) if prefix_content.is_empty() => None,
83 Some(mut prefix_content) => {
84 prefix_content.prepare();
85 if prefix_content.is_empty() {
86 None
87 } else {
88 Some(prefix_content)
89 }
90 }
91 None => None,
92 };
93 self.suffix_content = match self.suffix_content.take() {
94 Some(suffix_content) if suffix_content.is_empty() => None,
95 Some(mut suffix_content) => {
96 suffix_content.prepare();
97 if suffix_content.is_empty() {
98 None
99 } else {
100 Some(suffix_content)
101 }
102 }
103 None => None,
104 };
105 self.mcp_servers = match self.mcp_servers.take() {
106 Some(mcp_servers) => {
107 super::super::mcp::mcp_servers::prepare(mcp_servers)
108 }
109 None => None,
110 };
111 self.laboratories = match self.laboratories.take() {
112 Some(laboratories) => {
113 super::super::laboratory::laboratories::prepare(laboratories)
114 }
115 None => None,
116 };
117 self.client_objectiveai_mcp = match self.client_objectiveai_mcp.take() {
118 Some(cm) => super::super::client_objectiveai_mcp::prepare(cm),
119 None => None,
120 };
121 }
122
123 pub fn validate(&self) -> Result<(), String> {
125 if self.model.is_empty() {
126 return Err("`model` string cannot be empty".to_string());
127 }
128 if let Some(effort) = &self.effort {
129 effort.validate()?;
130 }
131 if let Some(prefix_content) = &self.prefix_content {
132 prefix_content
133 .validate_text_or_image_only()
134 .map_err(|e| format!("`prefix_content`: {e}"))?;
135 }
136 if let Some(suffix_content) = &self.suffix_content {
137 suffix_content
138 .validate_text_or_image_only()
139 .map_err(|e| format!("`suffix_content`: {e}"))?;
140 }
141 if let Some(mcp_servers) = &self.mcp_servers {
142 super::super::mcp::mcp_servers::validate(mcp_servers)?;
143 }
144 if let Some(laboratories) = &self.laboratories {
145 super::super::laboratory::laboratories::validate(laboratories)?;
146 }
147 if let Some(cm) = &self.client_objectiveai_mcp {
148 super::super::client_objectiveai_mcp::validate(cm)?;
149 }
150 Ok(())
151 }
152
153 pub fn merged_messages(
158 &self,
159 messages: Vec<super::super::completions::message::Message>,
160 ) -> Vec<super::super::completions::message::Message> {
161 use super::super::completions::message::{Message, UserMessage};
162 let prefix_len = if self.prefix_content.is_some() { 1 } else { 0 };
163 let suffix_len = if self.suffix_content.is_some() { 1 } else { 0 };
164 let mut merged =
165 Vec::with_capacity(prefix_len + messages.len() + suffix_len);
166 if let Some(prefix_content) = &self.prefix_content {
167 merged.push(Message::User(UserMessage {
168 content: prefix_content.clone(),
169 }));
170 }
171 merged.extend(messages);
172 if let Some(suffix_content) = &self.suffix_content {
173 merged.push(Message::User(UserMessage {
174 content: suffix_content.clone(),
175 }));
176 }
177 merged
178 }
179
180 pub fn id(&self) -> String {
182 let mut hasher = XxHash3_128::with_seed(0);
183 hasher.write(serde_json::to_string(self).unwrap().as_bytes());
184 format!("{:0>22}", base62::encode(hasher.finish_128()))
185 }
186}
187
188#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
190#[schemars(rename = "agent.codex_sdk.Agent")]
191pub struct Agent {
192 pub id: String,
194 #[serde(flatten)]
196 pub base: AgentBase,
197}
198
199impl TryFrom<AgentBase> for Agent {
200 type Error = String;
201 fn try_from(mut base: AgentBase) -> Result<Self, Self::Error> {
202 base.prepare();
203 base.validate()?;
204 let id = base.id();
205 Ok(Agent { id, base })
206 }
207}