1use std::sync::Arc;
11
12use everruns::LlmSimConfig;
13
14#[derive(Clone, Debug)]
17pub struct Markdown {
18 path: &'static str,
19 embedded: &'static str,
20 disk: &'static str,
21}
22
23impl Markdown {
24 #[doc(hidden)]
25 pub const fn __embedded(
26 path: &'static str,
27 embedded: &'static str,
28 disk: &'static str,
29 ) -> Self {
30 Self {
31 path,
32 embedded,
33 disk,
34 }
35 }
36
37 pub fn path(&self) -> &'static str {
39 self.path
40 }
41
42 pub fn load(&self, hot_reload: bool) -> String {
44 if hot_reload && let Ok(text) = std::fs::read_to_string(self.disk) {
45 return text;
46 }
47 self.embedded.to_string()
48 }
49}
50
51#[derive(Clone, Debug)]
53pub enum Instructions {
54 Text(String),
55 Markdown(Markdown),
56}
57
58impl Instructions {
59 pub(crate) fn load(&self, hot_reload: bool) -> String {
60 match self {
61 Instructions::Text(text) => text.clone(),
62 Instructions::Markdown(markdown) => markdown.load(hot_reload),
63 }
64 }
65}
66
67impl From<&str> for Instructions {
68 fn from(text: &str) -> Self {
69 Instructions::Text(text.to_string())
70 }
71}
72
73impl From<String> for Instructions {
74 fn from(text: String) -> Self {
75 Instructions::Text(text)
76 }
77}
78
79impl From<Markdown> for Instructions {
80 fn from(markdown: Markdown) -> Self {
81 Instructions::Markdown(markdown)
82 }
83}
84
85type Customize = Arc<dyn Fn(everruns::AgentBuilder) -> everruns::AgentBuilder + Send + Sync>;
86
87#[derive(Clone)]
90pub struct Agent {
91 pub(crate) model: String,
92 pub(crate) instructions: Option<Instructions>,
93 pub(crate) description: Option<String>,
94 pub(crate) tools: Option<Vec<String>>,
95 pub(crate) offline: Option<LlmSimConfig>,
96 pub(crate) customize: Option<Customize>,
97 pub(crate) package: Option<everruns::AgentPackage>,
98}
99
100impl std::fmt::Debug for Agent {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 f.debug_struct("Agent")
103 .field("model", &self.model)
104 .field("instructions", &self.instructions)
105 .field("tools", &self.tools)
106 .finish_non_exhaustive()
107 }
108}
109
110impl Agent {
111 pub fn from_package(path: impl AsRef<std::path::Path>) -> crate::Result<Self> {
114 Self::package(everruns::AgentPackage::load(path)?)
115 }
116
117 pub fn package(package: everruns::AgentPackage) -> crate::Result<Self> {
119 let m = package.manifest();
120 if m.environments.is_some() {
121 anyhow::bail!("package environments require an explicit host binding");
122 }
123 if let Some(harness) = m.harness.as_deref()
124 && !matches!(harness, "base" | "conversation" | "worker-base" | "worker")
125 {
126 anyhow::bail!(
127 "package harness {harness} requires an explicit Framework session binding"
128 );
129 }
130 let model = m
131 .model
132 .as_ref()
133 .map(|m| {
134 if m.provider.as_str() == "llmsim" {
135 "sim".into()
136 } else {
137 format!("{}/{}", m.provider, m.model)
138 }
139 })
140 .unwrap_or_else(|| "sim".into());
141 let mut spec = Self::builder()
142 .model(model)
143 .instructions(m.instructions.clone())
144 .build();
145 spec.description = m.description.clone();
146 spec.tools = Some(m.tools.iter().map(|t| t.name().into()).collect());
148 spec.package = Some(package);
149 Ok(spec)
150 }
151
152 pub fn builder() -> AgentBuilder {
154 AgentBuilder {
155 agent: Agent {
156 model: String::new(),
157 instructions: None,
158 description: None,
159 tools: None,
160 offline: None,
161 customize: None,
162 package: None,
163 },
164 }
165 }
166
167 pub fn model(&self) -> &str {
169 &self.model
170 }
171}
172
173#[must_use]
175pub struct AgentBuilder {
176 agent: Agent,
177}
178
179impl AgentBuilder {
180 pub fn model(mut self, model: impl Into<String>) -> Self {
183 self.agent.model = model.into();
184 self
185 }
186
187 pub fn instructions(mut self, instructions: impl Into<Instructions>) -> Self {
190 self.agent.instructions = Some(instructions.into());
191 self
192 }
193
194 pub fn description(mut self, description: impl Into<String>) -> Self {
197 self.agent.description = Some(description.into());
198 self
199 }
200
201 pub fn tools<I, S>(mut self, names: I) -> Self
204 where
205 I: IntoIterator<Item = S>,
206 S: Into<String>,
207 {
208 self.agent.tools = Some(names.into_iter().map(Into::into).collect());
209 self
210 }
211
212 pub fn offline(mut self, script: LlmSimConfig) -> Self {
215 self.agent.offline = Some(script);
216 self
217 }
218
219 pub fn customize(
222 mut self,
223 f: impl Fn(everruns::AgentBuilder) -> everruns::AgentBuilder + Send + Sync + 'static,
224 ) -> Self {
225 self.agent.customize = Some(Arc::new(f));
226 self
227 }
228
229 pub fn build(self) -> Agent {
232 self.agent
233 }
234}