Skip to main content

objectiveai_sdk/agent/script/
agent.rs

1//! Script Agent types and validation logic.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use twox_hash::XxHash3_128;
6
7/// The base configuration for a Script Agent (without computed ID).
8#[derive(
9    Clone,
10    Debug,
11    PartialEq,
12    Serialize,
13    Deserialize,
14    JsonSchema,
15    arbitrary::Arbitrary,
16)]
17#[schemars(rename = "agent.script.AgentBase")]
18pub struct AgentBase {
19    /// The upstream provider marker.
20    pub upstream: super::Upstream,
21
22    /// The output mode for vector completions. Ignored for agent completions.
23    pub output_mode: super::OutputMode,
24
25    /// The code this agent runs on the CLIENT — a required
26    /// `type`-tagged enum, flattened so `type` + its payload (e.g.
27    /// `python`) sit directly on the agent object.
28    #[serde(flatten)]
29    #[schemars(schema_with = "crate::flatten_schema::<super::Script>")]
30    pub script: super::Script,
31
32    /// MCP servers the agent can connect to.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    #[schemars(extend("omitempty" = true))]
35    pub mcp_servers: Option<super::super::McpServers>,
36
37    /// Laboratories provisioned for the agent — each becomes a
38    /// client-side laboratory MCP server whose id DERIVES from the
39    /// agent's full id plus the spec (see
40    /// [`laboratories::derived_id`](super::super::laboratory::laboratories::derived_id)).
41    #[serde(skip_serializing_if = "Option::is_none")]
42    #[schemars(extend("omitempty" = true))]
43    pub laboratories: Option<super::super::Laboratories>,
44
45    /// Client-side ObjectiveAI MCP surface the calling client is
46    /// expected to expose locally back to the API (objectiveai
47    /// built-in, plus specific plugins / tools by owner+name+version).
48    #[serde(skip_serializing_if = "Option::is_none")]
49    #[schemars(extend("omitempty" = true))]
50    pub client_objectiveai_mcp: Option<super::super::ClientObjectiveaiMcp>,
51}
52
53impl AgentBase {
54    /// Normalizes the configuration for deterministic ID computation.
55    /// The script code itself is never normalized — whitespace is
56    /// significant.
57    pub fn prepare(&mut self) {
58        self.mcp_servers = match self.mcp_servers.take() {
59            Some(mcp_servers) => {
60                super::super::mcp::mcp_servers::prepare(mcp_servers)
61            }
62            None => None,
63        };
64        self.laboratories = match self.laboratories.take() {
65            Some(laboratories) => {
66                super::super::laboratory::laboratories::prepare(laboratories)
67            }
68            None => None,
69        };
70        self.client_objectiveai_mcp = match self.client_objectiveai_mcp.take() {
71            Some(cm) => super::super::client_objectiveai_mcp::prepare(cm),
72            None => None,
73        };
74    }
75
76    /// Validates the configuration.
77    pub fn validate(&self) -> Result<(), String> {
78        self.script.validate()?;
79        if let Some(mcp_servers) = &self.mcp_servers {
80            super::super::mcp::mcp_servers::validate(mcp_servers)?;
81        }
82        if let Some(laboratories) = &self.laboratories {
83            super::super::laboratory::laboratories::validate(laboratories)?;
84        }
85        if let Some(cm) = &self.client_objectiveai_mcp {
86            super::super::client_objectiveai_mcp::validate(cm)?;
87        }
88        Ok(())
89    }
90
91    /// Returns the messages as-is.
92    pub fn merged_messages(
93        &self,
94        messages: Vec<super::super::completions::message::Message>,
95    ) -> Vec<super::super::completions::message::Message> {
96        messages
97    }
98
99    /// Computes the deterministic content-addressed ID.
100    pub fn id(&self) -> String {
101        let mut hasher = XxHash3_128::with_seed(0);
102        hasher.write(serde_json::to_string(self).unwrap().as_bytes());
103        format!("{:0>22}", base62::encode(hasher.finish_128()))
104    }
105
106    pub const fn model() -> &'static str {
107        "script"
108    }
109}
110
111/// A validated Script Agent with its computed content-addressed ID.
112#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
113#[schemars(rename = "agent.script.Agent")]
114pub struct Agent {
115    /// The deterministic content-addressed ID (22-character base62 string).
116    pub id: String,
117    /// The normalized configuration.
118    #[serde(flatten)]
119    pub base: AgentBase,
120}
121
122impl TryFrom<AgentBase> for Agent {
123    type Error = String;
124    fn try_from(mut base: AgentBase) -> Result<Self, Self::Error> {
125        base.prepare();
126        base.validate()?;
127        let id = base.id();
128        Ok(Agent { id, base })
129    }
130}