vv-agent 0.6.1

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use crate::agent::Agent;
use crate::config::ResolvedModelConfig;
use crate::run_config::RunConfig;
use crate::types::{AgentResult, AgentStatus};

#[derive(Clone)]
pub struct RunResult {
    agent_name: String,
    result: AgentResult,
    resolved: ResolvedModelConfig,
    resume_context: Option<RunResumeContext>,
}

impl RunResult {
    pub fn new(
        agent_name: impl Into<String>,
        result: AgentResult,
        resolved: ResolvedModelConfig,
    ) -> Self {
        Self {
            agent_name: agent_name.into(),
            result,
            resolved,
            resume_context: None,
        }
    }

    pub fn agent_name(&self) -> &str {
        &self.agent_name
    }

    pub fn status(&self) -> AgentStatus {
        self.result.status
    }

    pub fn final_output(&self) -> Option<&str> {
        self.result.final_answer.as_deref()
    }

    pub fn result(&self) -> &AgentResult {
        &self.result
    }

    pub fn resolved_model(&self) -> &ResolvedModelConfig {
        &self.resolved
    }

    pub fn into_state(self) -> Result<RunState, String> {
        RunState::from_result(self)
    }

    pub(crate) fn with_resume_context(mut self, context: RunResumeContext) -> Self {
        self.resume_context = Some(context);
        self
    }

    pub(crate) fn resume_context(&self) -> Option<&RunResumeContext> {
        self.resume_context.as_ref()
    }
}

#[derive(Clone)]
pub struct RunState {
    result: RunResult,
    approved_interruption_ids: Vec<String>,
}

impl RunState {
    pub fn from_result(result: RunResult) -> Result<Self, String> {
        if result.status() != AgentStatus::WaitUser {
            return Err("only interrupted runs can be converted into RunState".to_string());
        }
        Ok(Self {
            result,
            approved_interruption_ids: Vec::new(),
        })
    }

    pub fn result(&self) -> &RunResult {
        &self.result
    }

    pub fn approve(&mut self, interruption_id: &str) -> Result<(), String> {
        if !self
            .pending_approval_ids()
            .iter()
            .any(|id| id == interruption_id)
        {
            return Err(format!("unknown approval interruption: {interruption_id}"));
        }
        if !self
            .approved_interruption_ids
            .iter()
            .any(|id| id == interruption_id)
        {
            self.approved_interruption_ids
                .push(interruption_id.to_string());
        }
        Ok(())
    }

    pub fn approved_interruption_ids(&self) -> &[String] {
        &self.approved_interruption_ids
    }

    pub fn pending_approval_ids(&self) -> Vec<String> {
        self.result
            .result()
            .cycles
            .iter()
            .flat_map(|cycle| cycle.tool_results.iter())
            .filter_map(|tool_result| {
                tool_result
                    .metadata
                    .get("approval_interruption_id")
                    .and_then(serde_json::Value::as_str)
                    .map(str::to_string)
            })
            .collect()
    }

    pub(crate) fn into_inner(self) -> (RunResult, Vec<String>) {
        (self.result, self.approved_interruption_ids)
    }
}

#[derive(Clone)]
pub(crate) struct RunResumeContext {
    pub agent: Agent,
    pub input: crate::runner::NormalizedInput,
    pub config: RunConfig,
}