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::{
29 AgentCheckpoint, AgentCheckpointPhase, AgentCheckpointState, DurableConversationCheckpoint,
30 ResumePolicy,
31};
32use crate::{
33 AgentError, AgentEventStream, AgentGateway, AgentOutcome, AgentStreamEvent, CallableKind,
34 GatewayError, GatewayErrorKind, StructuredAgent,
35};
36
37mod callable;
38mod checkpointing;
39mod execution;
40mod observability;
41mod retrieval;
42
43#[cfg(not(target_arch = "wasm32"))]
45pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
46
47#[cfg(target_arch = "wasm32")]
49pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
50
51#[derive(Clone)]
53pub(crate) struct DynamicContext {
54 pub(crate) limit: usize,
55 pub(crate) retriever: Arc<dyn Retriever>,
56}
57
58impl std::fmt::Debug for DynamicContext {
59 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 formatter
61 .debug_struct("DynamicContext")
62 .field("limit", &self.limit)
63 .field("retriever", self.retriever.descriptor())
64 .finish()
65 }
66}
67
68#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
70#[non_exhaustive]
71pub enum ToolErrorPolicy {
72 #[default]
74 ReturnToModel,
75 FailFast,
77}
78
79#[derive(Clone, Debug, Eq, PartialEq)]
81pub struct AgentConfig {
82 pub max_turns: u32,
84 pub tool_error_policy: ToolErrorPolicy,
86 pub feature_policy: FeaturePolicy,
88}
89
90impl Default for AgentConfig {
91 fn default() -> Self {
92 Self {
93 max_turns: 16,
94 tool_error_policy: ToolErrorPolicy::ReturnToModel,
95 feature_policy: FeaturePolicy::Strict,
96 }
97 }
98}
99
100#[derive(Clone)]
102pub struct Agent {
103 pub(crate) name: String,
104 pub(crate) model: Arc<dyn Model>,
105 pub(crate) model_ref: ModelRef,
106 pub(crate) instructions: Vec<Message>,
107 pub(crate) context: Vec<Document>,
108 pub(crate) dynamic_context: Vec<DynamicContext>,
109 pub(crate) tools: ToolRegistry,
110 pub(crate) agents: AgentGateway,
111 pub(crate) effects: EffectExecutor,
112 pub(crate) effect_recovery: EffectRecoveryPolicy,
113 pub(crate) config: AgentConfig,
114 pub(crate) output_format: OutputFormat,
115}
116
117impl Agent {
118 pub fn builder(
120 name: impl Into<String>,
121 model: Arc<dyn Model>,
122 model_ref: ModelRef,
123 ) -> crate::AgentBuilder {
124 crate::AgentBuilder::new(name, model, model_ref)
125 }
126
127 pub fn new(name: impl Into<String>, model: Arc<dyn Model>, model_ref: ModelRef) -> Self {
129 Self {
130 name: name.into(),
131 model,
132 model_ref,
133 instructions: Vec::new(),
134 context: Vec::new(),
135 dynamic_context: Vec::new(),
136 tools: ToolRegistry::new(),
137 agents: AgentGateway::new(),
138 effects: EffectExecutor::new(Arc::new(InMemoryEffectStore::new())),
139 effect_recovery: EffectRecoveryPolicy::RejectAmbiguous,
140 config: AgentConfig::default(),
141 output_format: OutputFormat::Text,
142 }
143 }
144
145 #[must_use]
147 pub fn system(mut self, instruction: impl Into<String>) -> Self {
148 self.instructions.push(Message::system(instruction));
149 self
150 }
151
152 #[must_use]
154 pub fn tools(mut self, tools: ToolRegistry) -> Self {
155 self.tools = tools;
156 self
157 }
158
159 #[must_use]
161 pub fn agents(mut self, agents: AgentGateway) -> Self {
162 self.agents = agents;
163 self
164 }
165
166 #[must_use]
168 pub fn effect_executor(mut self, effects: EffectExecutor) -> Self {
169 self.effects = effects;
170 self
171 }
172
173 #[must_use]
175 pub const fn effect_recovery_policy(mut self, policy: EffectRecoveryPolicy) -> Self {
176 self.effect_recovery = policy;
177 self
178 }
179
180 #[must_use]
182 pub const fn with_config(mut self, config: AgentConfig) -> Self {
183 self.config = config;
184 self
185 }
186
187 #[must_use]
189 pub fn output_format(mut self, output_format: OutputFormat) -> Self {
190 self.output_format = output_format;
191 self
192 }
193
194 #[must_use]
196 pub fn structured_output<T>(self, name: impl Into<String>) -> Self
197 where
198 T: JsonSchema,
199 {
200 self.output_format(OutputFormat::typed::<T>(name))
201 }
202
203 pub fn into_structured<T>(self, name: impl Into<String>) -> StructuredAgent<T>
205 where
206 T: JsonSchema,
207 {
208 StructuredAgent::new(self.structured_output::<T>(name))
209 }
210
211 pub fn name(&self) -> &str {
213 &self.name
214 }
215
216 pub const fn model_ref(&self) -> &ModelRef {
218 &self.model_ref
219 }
220
221 pub fn callable_capabilities(&self) -> CapabilitySet {
227 let mut capabilities = CapabilitySet::new();
228 for spec in self.tools.model_specs() {
229 if let Some(descriptor) = self.tools.descriptor(&spec.name) {
230 capabilities.grant(descriptor.capability());
231 }
232 }
233 for spec in self.agents.model_specs() {
234 if let Some(descriptor) = self.agents.descriptor(&spec.name) {
235 capabilities.grant(descriptor.capability());
236 }
237 }
238 for source in &self.dynamic_context {
239 capabilities.grant(source.retriever.descriptor().capability());
240 }
241 capabilities
242 }
243
244 #[must_use]
252 pub fn default_run_context(&self) -> RunContext {
253 RunContext::root(
254 BudgetTracker::new(Budget::default()),
255 self.callable_capabilities(),
256 )
257 }
258}
259
260impl std::fmt::Debug for Agent {
261 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 formatter
263 .debug_struct("Agent")
264 .field("name", &self.name)
265 .field("model_ref", &self.model_ref)
266 .field("instructions", &self.instructions)
267 .field("context", &self.context)
268 .field("dynamic_context", &self.dynamic_context)
269 .field("tools", &self.tools)
270 .field("agents", &self.agents)
271 .field("effects", &self.effects)
272 .field("effect_recovery", &self.effect_recovery)
273 .field("config", &self.config)
274 .field("output_format", &self.output_format)
275 .finish_non_exhaustive()
276 }
277}
278
279#[cfg(test)]
280mod tests;