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, GenerationOptions, Message, Model, ModelCallContext, ModelError,
18 ModelErrorKind, ModelRef, ModelRequest, ModelResponse, ModelStreamAccumulator, OutputFormat,
19 ProviderToolSpec, ResponseMode, Role, ToolCall, 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 pub(crate) generation: GenerationOptions,
116 pub(crate) response_mode: ResponseMode,
117 pub(crate) provider_tools: Vec<ProviderToolSpec>,
118 pub(crate) provider_options: BTreeMap<String, serde_json::Value>,
119}
120
121impl Agent {
122 pub fn builder(
124 name: impl Into<String>,
125 model: Arc<dyn Model>,
126 model_ref: ModelRef,
127 ) -> crate::AgentBuilder {
128 crate::AgentBuilder::new(name, model, model_ref)
129 }
130
131 pub fn new(name: impl Into<String>, model: Arc<dyn Model>, model_ref: ModelRef) -> Self {
133 Self {
134 name: name.into(),
135 model,
136 model_ref,
137 instructions: Vec::new(),
138 context: Vec::new(),
139 dynamic_context: Vec::new(),
140 tools: ToolRegistry::new(),
141 agents: AgentGateway::new(),
142 effects: EffectExecutor::new(Arc::new(InMemoryEffectStore::new())),
143 effect_recovery: EffectRecoveryPolicy::RejectAmbiguous,
144 config: AgentConfig::default(),
145 output_format: OutputFormat::Text,
146 generation: GenerationOptions::default(),
147 response_mode: ResponseMode::Streaming,
148 provider_tools: Vec::new(),
149 provider_options: BTreeMap::new(),
150 }
151 }
152
153 #[must_use]
155 pub fn system(mut self, instruction: impl Into<String>) -> Self {
156 self.instructions.push(Message::system(instruction));
157 self
158 }
159
160 #[must_use]
162 pub fn tools(mut self, tools: ToolRegistry) -> Self {
163 self.tools = tools;
164 self
165 }
166
167 #[must_use]
169 pub fn agents(mut self, agents: AgentGateway) -> Self {
170 self.agents = agents;
171 self
172 }
173
174 #[must_use]
176 pub fn effect_executor(mut self, effects: EffectExecutor) -> Self {
177 self.effects = effects;
178 self
179 }
180
181 #[must_use]
183 pub const fn effect_recovery_policy(mut self, policy: EffectRecoveryPolicy) -> Self {
184 self.effect_recovery = policy;
185 self
186 }
187
188 #[must_use]
190 pub const fn with_config(mut self, config: AgentConfig) -> Self {
191 self.config = config;
192 self
193 }
194
195 #[must_use]
197 pub fn output_format(mut self, output_format: OutputFormat) -> Self {
198 self.output_format = output_format;
199 self
200 }
201
202 #[must_use]
204 pub fn structured_output<T>(self, name: impl Into<String>) -> Self
205 where
206 T: JsonSchema,
207 {
208 self.output_format(OutputFormat::typed::<T>(name))
209 }
210
211 pub fn into_structured<T>(self, name: impl Into<String>) -> StructuredAgent<T>
213 where
214 T: JsonSchema,
215 {
216 StructuredAgent::new(self.structured_output::<T>(name))
217 }
218
219 pub fn name(&self) -> &str {
221 &self.name
222 }
223
224 pub const fn model_ref(&self) -> &ModelRef {
226 &self.model_ref
227 }
228
229 pub fn callable_capabilities(&self) -> CapabilitySet {
235 let mut capabilities = CapabilitySet::new();
236 for spec in self.tools.model_specs() {
237 if let Some(descriptor) = self.tools.descriptor(&spec.name) {
238 capabilities.grant(descriptor.capability());
239 }
240 }
241 for spec in self.agents.model_specs() {
242 if let Some(descriptor) = self.agents.descriptor(&spec.name) {
243 capabilities.grant(descriptor.capability());
244 }
245 }
246 for source in &self.dynamic_context {
247 capabilities.grant(source.retriever.descriptor().capability());
248 }
249 capabilities
250 }
251
252 #[must_use]
260 pub fn default_run_context(&self) -> RunContext {
261 RunContext::root(
262 BudgetTracker::new(Budget::default()),
263 self.callable_capabilities(),
264 )
265 }
266}
267
268impl std::fmt::Debug for Agent {
269 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 formatter
271 .debug_struct("Agent")
272 .field("name", &self.name)
273 .field("model_ref", &self.model_ref)
274 .field("instructions", &self.instructions)
275 .field("context", &self.context)
276 .field("dynamic_context", &self.dynamic_context)
277 .field("tools", &self.tools)
278 .field("agents", &self.agents)
279 .field("effects", &self.effects)
280 .field("effect_recovery", &self.effect_recovery)
281 .field("config", &self.config)
282 .field("output_format", &self.output_format)
283 .finish_non_exhaustive()
284 }
285}
286
287#[cfg(test)]
288mod tests;