use std::{collections::BTreeMap, future::Future, pin::Pin, sync::Arc, time::Instant};
use futures_util::{
StreamExt,
future::{Either, select},
};
use runifold_core::{
BudgetEvent, CapabilitySet, DomainEvent, EffectId, EffectKind, EffectRequest, EventId,
InvocationId, LifecycleEvent, RetrySafety, RunContext, RunError, RunErrorKind, RunEventKind,
Usage,
};
use runifold_effect::{
EffectExecutionContext, EffectExecutor, EffectExecutorErrorKind, EffectFuture, EffectHandler,
EffectRecoveryPolicy, InMemoryEffectStore,
};
use runifold_model::{
ContentPart, FeaturePolicy, Message, Model, ModelCallContext, ModelError, ModelErrorKind,
ModelRef, ModelRequest, ModelResponse, ModelStreamAccumulator, OutputFormat, Role, ToolCall,
ToolResult,
};
use runifold_tool::{ToolError, ToolErrorKind, ToolOutput, ToolRegistry};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::checkpoint::CheckpointCursor;
use crate::stream::{AgentObserver, BufferedObserver, NoopObserver, emit_agent_event};
use crate::{AgentCheckpoint, AgentCheckpointPhase, AgentCheckpointState, ResumePolicy};
use crate::{
AgentError, AgentEventStream, AgentGateway, AgentOutcome, AgentStreamEvent, CallableKind,
GatewayError, GatewayErrorKind, StructuredAgent,
};
mod callable;
mod checkpointing;
mod execution;
mod observability;
pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum ToolErrorPolicy {
#[default]
ReturnToModel,
FailFast,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AgentConfig {
pub max_turns: u32,
pub tool_error_policy: ToolErrorPolicy,
pub feature_policy: FeaturePolicy,
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
max_turns: 16,
tool_error_policy: ToolErrorPolicy::ReturnToModel,
feature_policy: FeaturePolicy::Strict,
}
}
}
#[derive(Clone)]
pub struct Agent {
pub(crate) name: String,
pub(crate) model: Arc<dyn Model>,
pub(crate) model_ref: ModelRef,
pub(crate) instructions: Vec<Message>,
pub(crate) tools: ToolRegistry,
pub(crate) agents: AgentGateway,
pub(crate) effects: EffectExecutor,
pub(crate) effect_recovery: EffectRecoveryPolicy,
pub(crate) config: AgentConfig,
pub(crate) output_format: OutputFormat,
}
impl Agent {
pub fn builder(
name: impl Into<String>,
model: Arc<dyn Model>,
model_ref: ModelRef,
) -> crate::AgentBuilder {
crate::AgentBuilder::new(name, model, model_ref)
}
pub fn new(name: impl Into<String>, model: Arc<dyn Model>, model_ref: ModelRef) -> Self {
Self {
name: name.into(),
model,
model_ref,
instructions: Vec::new(),
tools: ToolRegistry::new(),
agents: AgentGateway::new(),
effects: EffectExecutor::new(Arc::new(InMemoryEffectStore::new())),
effect_recovery: EffectRecoveryPolicy::RejectAmbiguous,
config: AgentConfig::default(),
output_format: OutputFormat::Text,
}
}
#[must_use]
pub fn system(mut self, instruction: impl Into<String>) -> Self {
self.instructions.push(Message::system(instruction));
self
}
#[must_use]
pub fn tools(mut self, tools: ToolRegistry) -> Self {
self.tools = tools;
self
}
#[must_use]
pub fn agents(mut self, agents: AgentGateway) -> Self {
self.agents = agents;
self
}
#[must_use]
pub fn effect_executor(mut self, effects: EffectExecutor) -> Self {
self.effects = effects;
self
}
#[must_use]
pub const fn effect_recovery_policy(mut self, policy: EffectRecoveryPolicy) -> Self {
self.effect_recovery = policy;
self
}
#[must_use]
pub const fn with_config(mut self, config: AgentConfig) -> Self {
self.config = config;
self
}
#[must_use]
pub fn output_format(mut self, output_format: OutputFormat) -> Self {
self.output_format = output_format;
self
}
#[must_use]
pub fn structured_output<T>(self, name: impl Into<String>) -> Self
where
T: JsonSchema,
{
self.output_format(OutputFormat::typed::<T>(name))
}
pub fn into_structured<T>(self, name: impl Into<String>) -> StructuredAgent<T>
where
T: JsonSchema,
{
StructuredAgent::new(self.structured_output::<T>(name))
}
pub fn name(&self) -> &str {
&self.name
}
pub const fn model_ref(&self) -> &ModelRef {
&self.model_ref
}
pub fn callable_capabilities(&self) -> CapabilitySet {
let mut capabilities = CapabilitySet::new();
for spec in self.tools.model_specs() {
if let Some(descriptor) = self.tools.descriptor(&spec.name) {
capabilities.grant(descriptor.capability());
}
}
for spec in self.agents.model_specs() {
if let Some(descriptor) = self.agents.descriptor(&spec.name) {
capabilities.grant(descriptor.capability());
}
}
capabilities
}
}
impl std::fmt::Debug for Agent {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Agent")
.field("name", &self.name)
.field("model_ref", &self.model_ref)
.field("instructions", &self.instructions)
.field("tools", &self.tools)
.field("agents", &self.agents)
.field("effects", &self.effects)
.field("effect_recovery", &self.effect_recovery)
.field("config", &self.config)
.field("output_format", &self.output_format)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests;