use std::fmt;
use serde_json::Value;
use crate::chat::ChatResponse;
use crate::guardrail::{
InputGuardrail, InputGuardrailResult, OutputGuardrail, OutputGuardrailResult,
};
use crate::hooks::SharedHooks;
use crate::memory::SharedSession;
use crate::message::{Content, ContentPart, ImageMime, Message, Role, ToolCall};
use crate::tool::SharedConfirmationHandler;
use crate::usage::Usage;
#[derive(Debug)]
pub enum NextStep {
FinalOutput {
output: Value,
},
ToolCalls {
calls: Vec<ToolCallRequest>,
},
NeedsApproval {
pending_approval: Vec<ToolCallRequest>,
approved: Vec<ToolCallRequest>,
},
}
#[derive(Debug, Clone)]
pub struct ToolCallRequest {
pub id: String,
pub name: String,
pub arguments: Value,
}
impl From<&ToolCall> for ToolCallRequest {
fn from(tc: &ToolCall) -> Self {
let arguments: Value = serde_json::from_str(&tc.function.arguments).unwrap_or(Value::Null);
Self {
id: tc.id.clone(),
name: tc.function.name.clone(),
arguments,
}
}
}
#[derive(Clone, Default)]
pub struct RunConfig {
pub hooks: Option<SharedHooks>,
pub session: Option<SharedSession>,
pub max_steps: Option<usize>,
pub max_tool_concurrency: Option<usize>,
pub confirmation_handler: Option<SharedConfirmationHandler>,
pub input_guardrails: Vec<InputGuardrail>,
pub output_guardrails: Vec<OutputGuardrail>,
}
impl fmt::Debug for RunConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RunConfig")
.field("hooks", &self.hooks.is_some())
.field("session", &self.session.is_some())
.field("max_steps", &self.max_steps)
.field("max_tool_concurrency", &self.max_tool_concurrency)
.field("confirmation_handler", &self.confirmation_handler.is_some())
.field("input_guardrails", &self.input_guardrails.len())
.field("output_guardrails", &self.output_guardrails.len())
.finish()
}
}
impl RunConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn hooks(mut self, hooks: SharedHooks) -> Self {
self.hooks = Some(hooks);
self
}
#[must_use]
pub fn session(mut self, session: SharedSession) -> Self {
self.session = Some(session);
self
}
#[must_use]
pub const fn max_steps(mut self, max_steps: usize) -> Self {
self.max_steps = Some(max_steps);
self
}
#[must_use]
pub const fn max_tool_concurrency(mut self, max: usize) -> Self {
self.max_tool_concurrency = Some(max);
self
}
#[must_use]
pub fn confirmation_handler(mut self, handler: SharedConfirmationHandler) -> Self {
self.confirmation_handler = Some(handler);
self
}
#[must_use]
pub fn input_guardrail(mut self, guardrail: InputGuardrail) -> Self {
self.input_guardrails.push(guardrail);
self
}
#[must_use]
pub fn output_guardrail(mut self, guardrail: OutputGuardrail) -> Self {
self.output_guardrails.push(guardrail);
self
}
}
#[derive(Debug)]
pub struct RunResult {
pub output: Value,
pub usage: Usage,
pub steps: usize,
pub step_history: Vec<StepInfo>,
pub agent_name: String,
pub input_guardrail_results: Vec<InputGuardrailResult>,
pub output_guardrail_results: Vec<OutputGuardrailResult>,
}
impl RunResult {
#[must_use]
pub fn text(&self) -> Option<&str> {
self.output.as_str()
}
pub fn parse<T: serde::de::DeserializeOwned>(&self) -> serde_json::Result<T> {
serde_json::from_value(self.output.clone())
}
}
#[derive(Debug, Clone)]
pub struct StepInfo {
pub step: usize,
pub response: ChatResponse,
pub tool_calls: Vec<ToolCallRecord>,
}
#[derive(Debug, Clone)]
pub struct ToolCallRecord {
pub id: String,
pub name: String,
pub arguments: Value,
pub result: String,
pub success: bool,
pub sub_usage: Usage,
}
#[derive(Debug)]
pub enum RunEvent {
RunStarted {
agent_name: String,
},
StepStarted {
step: usize,
},
TextDelta(String),
ReasoningDelta(String),
AudioDelta {
data: String,
transcript: Option<String>,
},
ToolCallStarted {
id: String,
name: String,
},
ToolCallCompleted {
record: ToolCallRecord,
},
StepCompleted {
step_info: Box<StepInfo>,
},
RunCompleted {
result: Box<RunResult>,
},
}
#[derive(Debug, Clone)]
pub enum UserInput {
Text(String),
Parts(Vec<ContentPart>),
}
impl UserInput {
#[inline]
#[must_use]
pub fn text(text: impl Into<String>) -> Self {
Self::Text(text.into())
}
#[inline]
#[must_use]
pub const fn parts(parts: Vec<ContentPart>) -> Self {
Self::Parts(parts)
}
#[must_use]
pub fn with_image(text: impl Into<String>, image_url: impl Into<String>) -> Self {
Self::Parts(vec![
ContentPart::text(text),
ContentPart::image_url(image_url),
])
}
#[must_use]
pub fn with_image_bytes(text: impl Into<String>, data: &[u8], mime: ImageMime) -> Self {
Self::Parts(vec![
ContentPart::text(text),
ContentPart::image_bytes(data, mime),
])
}
#[must_use]
pub fn with_image_auto(text: impl Into<String>, data: &[u8]) -> Self {
Self::Parts(vec![
ContentPart::text(text),
ContentPart::image_bytes_auto(data),
])
}
#[must_use]
pub fn into_message(self) -> Message {
match self {
Self::Text(text) => Message::user(text),
Self::Parts(parts) => Message::new(Role::User, Content::Parts(parts)),
}
}
#[must_use]
pub fn has_images(&self) -> bool {
match self {
Self::Text(_) => false,
Self::Parts(parts) => parts.iter().any(ContentPart::is_image),
}
}
#[must_use]
pub fn has_audio(&self) -> bool {
match self {
Self::Text(_) => false,
Self::Parts(parts) => parts.iter().any(ContentPart::is_audio),
}
}
#[must_use]
pub fn is_multimodal(&self) -> bool {
self.has_images() || self.has_audio()
}
}
impl From<&str> for UserInput {
#[inline]
fn from(s: &str) -> Self {
Self::Text(s.to_owned())
}
}
impl From<String> for UserInput {
#[inline]
fn from(s: String) -> Self {
Self::Text(s)
}
}
impl From<Vec<ContentPart>> for UserInput {
#[inline]
fn from(parts: Vec<ContentPart>) -> Self {
Self::Parts(parts)
}
}