1use std::{collections::BTreeMap, future::Future, pin::Pin, sync::Arc};
2
3use futures_util::{
4 StreamExt,
5 future::{Either, select},
6};
7use runifold_core::{
8 Budget, BudgetEvent, BudgetTracker, CapabilitySet, DomainEvent, EffectId, EffectKind,
9 EffectRequest, EventId, Instant, InvocationId, LifecycleEvent, RetrySafety, RunContext,
10 RunError, RunErrorKind, RunEventKind, Usage,
11};
12use runifold_effect::{
13 EffectExecutionContext, EffectExecutor, EffectExecutorErrorKind, EffectFuture, EffectHandler,
14 EffectRecoveryPolicy, InMemoryEffectStore,
15};
16use runifold_model::{
17 ContentPart, FeaturePolicy, Message, Model, ModelCallContext, ModelError, ModelErrorKind,
18 ModelRef, ModelRequest, ModelResponse, ModelStreamAccumulator, OutputFormat, Role, ToolCall,
19 ToolResult,
20};
21use runifold_retrieval::{Document, Retriever};
22use runifold_tool::{ToolError, ToolErrorKind, ToolOutput, ToolRegistry};
23use schemars::JsonSchema;
24use serde::{Deserialize, Serialize};
25
26use crate::checkpoint::CheckpointCursor;
27use crate::stream::{AgentObserver, BufferedObserver, NoopObserver, emit_agent_event};
28use crate::{AgentCheckpoint, AgentCheckpointPhase, AgentCheckpointState, ResumePolicy};
29use crate::{
30 AgentError, AgentEventStream, AgentGateway, AgentOutcome, AgentStreamEvent, CallableKind,
31 GatewayError, GatewayErrorKind, StructuredAgent,
32};
33
34mod callable;
35mod checkpointing;
36mod execution;
37mod observability;
38mod retrieval;
39
40#[cfg(not(target_arch = "wasm32"))]
42pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
43
44#[cfg(target_arch = "wasm32")]
46pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
47
48#[derive(Clone)]
50pub(crate) struct DynamicContext {
51 pub(crate) limit: usize,
52 pub(crate) retriever: Arc<dyn Retriever>,
53}
54
55impl std::fmt::Debug for DynamicContext {
56 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 formatter
58 .debug_struct("DynamicContext")
59 .field("limit", &self.limit)
60 .field("retriever", self.retriever.descriptor())
61 .finish()
62 }
63}
64
65#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
67#[non_exhaustive]
68pub enum ToolErrorPolicy {
69 #[default]
71 ReturnToModel,
72 FailFast,
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct AgentConfig {
79 pub max_turns: u32,
81 pub tool_error_policy: ToolErrorPolicy,
83 pub feature_policy: FeaturePolicy,
85}
86
87impl Default for AgentConfig {
88 fn default() -> Self {
89 Self {
90 max_turns: 16,
91 tool_error_policy: ToolErrorPolicy::ReturnToModel,
92 feature_policy: FeaturePolicy::Strict,
93 }
94 }
95}
96
97#[derive(Clone)]
99pub struct Agent {
100 pub(crate) name: String,
101 pub(crate) model: Arc<dyn Model>,
102 pub(crate) model_ref: ModelRef,
103 pub(crate) instructions: Vec<Message>,
104 pub(crate) context: Vec<Document>,
105 pub(crate) dynamic_context: Vec<DynamicContext>,
106 pub(crate) tools: ToolRegistry,
107 pub(crate) agents: AgentGateway,
108 pub(crate) effects: EffectExecutor,
109 pub(crate) effect_recovery: EffectRecoveryPolicy,
110 pub(crate) config: AgentConfig,
111 pub(crate) output_format: OutputFormat,
112}
113
114impl Agent {
115 pub fn builder(
117 name: impl Into<String>,
118 model: Arc<dyn Model>,
119 model_ref: ModelRef,
120 ) -> crate::AgentBuilder {
121 crate::AgentBuilder::new(name, model, model_ref)
122 }
123
124 pub fn new(name: impl Into<String>, model: Arc<dyn Model>, model_ref: ModelRef) -> Self {
126 Self {
127 name: name.into(),
128 model,
129 model_ref,
130 instructions: Vec::new(),
131 context: Vec::new(),
132 dynamic_context: Vec::new(),
133 tools: ToolRegistry::new(),
134 agents: AgentGateway::new(),
135 effects: EffectExecutor::new(Arc::new(InMemoryEffectStore::new())),
136 effect_recovery: EffectRecoveryPolicy::RejectAmbiguous,
137 config: AgentConfig::default(),
138 output_format: OutputFormat::Text,
139 }
140 }
141
142 #[must_use]
144 pub fn system(mut self, instruction: impl Into<String>) -> Self {
145 self.instructions.push(Message::system(instruction));
146 self
147 }
148
149 #[must_use]
151 pub fn tools(mut self, tools: ToolRegistry) -> Self {
152 self.tools = tools;
153 self
154 }
155
156 #[must_use]
158 pub fn agents(mut self, agents: AgentGateway) -> Self {
159 self.agents = agents;
160 self
161 }
162
163 #[must_use]
165 pub fn effect_executor(mut self, effects: EffectExecutor) -> Self {
166 self.effects = effects;
167 self
168 }
169
170 #[must_use]
172 pub const fn effect_recovery_policy(mut self, policy: EffectRecoveryPolicy) -> Self {
173 self.effect_recovery = policy;
174 self
175 }
176
177 #[must_use]
179 pub const fn with_config(mut self, config: AgentConfig) -> Self {
180 self.config = config;
181 self
182 }
183
184 #[must_use]
186 pub fn output_format(mut self, output_format: OutputFormat) -> Self {
187 self.output_format = output_format;
188 self
189 }
190
191 #[must_use]
193 pub fn structured_output<T>(self, name: impl Into<String>) -> Self
194 where
195 T: JsonSchema,
196 {
197 self.output_format(OutputFormat::typed::<T>(name))
198 }
199
200 pub fn into_structured<T>(self, name: impl Into<String>) -> StructuredAgent<T>
202 where
203 T: JsonSchema,
204 {
205 StructuredAgent::new(self.structured_output::<T>(name))
206 }
207
208 pub fn name(&self) -> &str {
210 &self.name
211 }
212
213 pub const fn model_ref(&self) -> &ModelRef {
215 &self.model_ref
216 }
217
218 pub fn callable_capabilities(&self) -> CapabilitySet {
224 let mut capabilities = CapabilitySet::new();
225 for spec in self.tools.model_specs() {
226 if let Some(descriptor) = self.tools.descriptor(&spec.name) {
227 capabilities.grant(descriptor.capability());
228 }
229 }
230 for spec in self.agents.model_specs() {
231 if let Some(descriptor) = self.agents.descriptor(&spec.name) {
232 capabilities.grant(descriptor.capability());
233 }
234 }
235 for source in &self.dynamic_context {
236 capabilities.grant(source.retriever.descriptor().capability());
237 }
238 capabilities
239 }
240
241 #[must_use]
249 pub fn default_run_context(&self) -> RunContext {
250 RunContext::root(
251 BudgetTracker::new(Budget::default()),
252 self.callable_capabilities(),
253 )
254 }
255}
256
257impl std::fmt::Debug for Agent {
258 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 formatter
260 .debug_struct("Agent")
261 .field("name", &self.name)
262 .field("model_ref", &self.model_ref)
263 .field("instructions", &self.instructions)
264 .field("context", &self.context)
265 .field("dynamic_context", &self.dynamic_context)
266 .field("tools", &self.tools)
267 .field("agents", &self.agents)
268 .field("effects", &self.effects)
269 .field("effect_recovery", &self.effect_recovery)
270 .field("config", &self.config)
271 .field("output_format", &self.output_format)
272 .finish_non_exhaustive()
273 }
274}
275
276#[cfg(test)]
277mod tests;