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, ToolChoice, 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};
32
33const TOOL_RESULT_EXECUTION_ID_METADATA: &str = "runifold.agent.execution_id";
34use crate::{
35 AgentError, AgentEventStream, AgentGateway, AgentOutcome, AgentStreamEvent, CallableKind,
36 GatewayError, GatewayErrorKind, StructuredAgent,
37};
38
39mod callable;
40mod checkpointing;
41mod execution;
42mod observability;
43mod retrieval;
44
45#[cfg(not(target_arch = "wasm32"))]
47pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
48
49#[cfg(target_arch = "wasm32")]
51pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
52
53#[derive(Clone)]
55pub(crate) struct DynamicContext {
56 pub(crate) limit: usize,
57 pub(crate) retriever: Arc<dyn Retriever>,
58}
59
60impl std::fmt::Debug for DynamicContext {
61 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 formatter
63 .debug_struct("DynamicContext")
64 .field("limit", &self.limit)
65 .field("retriever", self.retriever.descriptor())
66 .finish()
67 }
68}
69
70#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
72#[non_exhaustive]
73pub enum ToolErrorPolicy {
74 #[default]
76 ReturnToModel,
77 FailFast,
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
83pub struct AgentConfig {
84 pub max_turns: u32,
86 pub tool_error_policy: ToolErrorPolicy,
88 pub feature_policy: FeaturePolicy,
90}
91
92impl Default for AgentConfig {
93 fn default() -> Self {
94 Self {
95 max_turns: 16,
96 tool_error_policy: ToolErrorPolicy::ReturnToModel,
97 feature_policy: FeaturePolicy::Strict,
98 }
99 }
100}
101
102#[derive(Clone)]
104pub struct Agent {
105 pub(crate) name: String,
106 pub(crate) model: Arc<dyn Model>,
107 pub(crate) model_ref: ModelRef,
108 pub(crate) instructions: Vec<Message>,
109 pub(crate) context: Vec<Document>,
110 pub(crate) dynamic_context: Vec<DynamicContext>,
111 pub(crate) tools: ToolRegistry,
112 pub(crate) agents: AgentGateway,
113 pub(crate) effects: EffectExecutor,
114 pub(crate) effect_recovery: EffectRecoveryPolicy,
115 pub(crate) config: AgentConfig,
116 pub(crate) min_successful_tool_calls: u32,
117 pub(crate) output_format: OutputFormat,
118 pub(crate) generation: GenerationOptions,
119 pub(crate) response_mode: ResponseMode,
120 pub(crate) provider_tools: Vec<ProviderToolSpec>,
121 pub(crate) provider_options: BTreeMap<String, serde_json::Value>,
122}
123
124impl Agent {
125 pub fn builder(
127 name: impl Into<String>,
128 model: Arc<dyn Model>,
129 model_ref: ModelRef,
130 ) -> crate::AgentBuilder {
131 crate::AgentBuilder::new(name, model, model_ref)
132 }
133
134 pub fn new(name: impl Into<String>, model: Arc<dyn Model>, model_ref: ModelRef) -> Self {
136 Self {
137 name: name.into(),
138 model,
139 model_ref,
140 instructions: Vec::new(),
141 context: Vec::new(),
142 dynamic_context: Vec::new(),
143 tools: ToolRegistry::new(),
144 agents: AgentGateway::new(),
145 effects: EffectExecutor::new(Arc::new(InMemoryEffectStore::new())),
146 effect_recovery: EffectRecoveryPolicy::RejectAmbiguous,
147 config: AgentConfig::default(),
148 min_successful_tool_calls: 0,
149 output_format: OutputFormat::Text,
150 generation: GenerationOptions::default(),
151 response_mode: ResponseMode::Streaming,
152 provider_tools: Vec::new(),
153 provider_options: BTreeMap::new(),
154 }
155 }
156
157 #[must_use]
159 pub fn system(mut self, instruction: impl Into<String>) -> Self {
160 self.instructions.push(Message::system(instruction));
161 self
162 }
163
164 #[must_use]
166 pub fn tools(mut self, tools: ToolRegistry) -> Self {
167 self.tools = tools;
168 self
169 }
170
171 #[must_use]
173 pub fn agents(mut self, agents: AgentGateway) -> Self {
174 self.agents = agents;
175 self
176 }
177
178 #[must_use]
180 pub fn effect_executor(mut self, effects: EffectExecutor) -> Self {
181 self.effects = effects;
182 self
183 }
184
185 #[must_use]
187 pub const fn effect_recovery_policy(mut self, policy: EffectRecoveryPolicy) -> Self {
188 self.effect_recovery = policy;
189 self
190 }
191
192 #[must_use]
194 pub const fn with_config(mut self, config: AgentConfig) -> Self {
195 self.config = config;
196 self
197 }
198
199 #[must_use]
205 pub const fn min_successful_tool_calls(mut self, minimum: u32) -> Self {
206 self.min_successful_tool_calls = minimum;
207 self
208 }
209
210 #[must_use]
212 pub fn output_format(mut self, output_format: OutputFormat) -> Self {
213 self.output_format = output_format;
214 self
215 }
216
217 #[must_use]
219 pub fn structured_output<T>(self, name: impl Into<String>) -> Self
220 where
221 T: JsonSchema,
222 {
223 self.output_format(OutputFormat::typed::<T>(name))
224 }
225
226 pub fn into_structured<T>(self, name: impl Into<String>) -> StructuredAgent<T>
228 where
229 T: JsonSchema,
230 {
231 StructuredAgent::new(self.structured_output::<T>(name))
232 }
233
234 pub fn name(&self) -> &str {
236 &self.name
237 }
238
239 pub const fn model_ref(&self) -> &ModelRef {
241 &self.model_ref
242 }
243
244 pub fn callable_capabilities(&self) -> CapabilitySet {
250 let mut capabilities = CapabilitySet::new();
251 for spec in self.tools.model_specs() {
252 if let Some(descriptor) = self.tools.descriptor(&spec.name) {
253 capabilities.grant(descriptor.capability());
254 }
255 }
256 for spec in self.agents.model_specs() {
257 if let Some(descriptor) = self.agents.descriptor(&spec.name) {
258 capabilities.grant(descriptor.capability());
259 }
260 }
261 for source in &self.dynamic_context {
262 capabilities.grant(source.retriever.descriptor().capability());
263 }
264 capabilities
265 }
266
267 #[must_use]
275 pub fn default_run_context(&self) -> RunContext {
276 RunContext::root(
277 BudgetTracker::new(Budget::default()),
278 self.callable_capabilities(),
279 )
280 }
281}
282
283impl std::fmt::Debug for Agent {
284 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 formatter
286 .debug_struct("Agent")
287 .field("name", &self.name)
288 .field("model_ref", &self.model_ref)
289 .field("instructions", &self.instructions)
290 .field("context", &self.context)
291 .field("dynamic_context", &self.dynamic_context)
292 .field("tools", &self.tools)
293 .field("agents", &self.agents)
294 .field("effects", &self.effects)
295 .field("effect_recovery", &self.effect_recovery)
296 .field("config", &self.config)
297 .field("output_format", &self.output_format)
298 .finish_non_exhaustive()
299 }
300}
301
302#[cfg(test)]
303mod tests;