vv-agent 0.8.0

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::budget::{BudgetEnforcementBoundary, BudgetExhaustion, BudgetUsageSnapshot};
use crate::checkpoint::{
    OperationKind, OperationState, ReconciliationDecisionKind, ResumeObservation, ToolIdempotency,
};
use crate::types::{AgentStatus, ModelCallOperation, TokenUsage, ToolDirective};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RunEventPayload {
    RunStarted {
        input: String,
    },
    RunStateChanged {
        state: String,
    },
    AgentStarted,
    CycleStarted,
    ModelCallStarted {
        call_id: String,
        operation_id: String,
        attempt: u32,
        operation: ModelCallOperation,
        backend: String,
        model: String,
    },
    ModelCallCompleted {
        call_id: String,
        operation_id: String,
        attempt: u32,
        operation: ModelCallOperation,
        backend: String,
        model: String,
        usage: TokenUsage,
    },
    ModelCallFailed {
        call_id: String,
        operation_id: String,
        attempt: u32,
        operation: ModelCallOperation,
        backend: String,
        model: String,
        outcome: ModelCallFailureOutcome,
        usage: TokenUsage,
        error_code: String,
    },
    Diagnostic {
        level: DiagnosticLevel,
        code: String,
        details: serde_json::Map<String, Value>,
    },
    AssistantDelta {
        delta: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        content_chars: Option<u64>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        estimated_tokens: Option<u64>,
    },
    ReasoningDelta {
        delta: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        reasoning_chars: Option<u64>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        estimated_tokens: Option<u64>,
    },
    ModelToolCallStarted {
        tool_call_id: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        tool_call_index: Option<u64>,
        tool_name: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        arguments_chars: Option<u64>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        estimated_tokens: Option<u64>,
    },
    ModelToolCallProgress {
        tool_call_id: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        tool_call_index: Option<u64>,
        tool_name: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        arguments_chars: Option<u64>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        estimated_tokens: Option<u64>,
    },
    ToolCallPlanned {
        tool_call_id: String,
        tool_name: String,
        arguments: Value,
    },
    ToolCallStarted {
        tool_call_id: String,
        tool_name: String,
        arguments: Value,
    },
    ApprovalRequested {
        request_id: String,
        tool_call_id: String,
        tool_name: String,
        message: String,
    },
    ApprovalResolved {
        request_id: String,
        tool_name: String,
        tool_call_id: String,
        action: ApprovalAction,
    },
    ToolCallCompleted {
        tool_call_id: String,
        tool_name: String,
        status: ToolStatus,
        directive: ToolDirective,
        error_code: Option<String>,
        execution_started: bool,
        duration_ms: Option<u64>,
    },
    MemoryCompactStarted {
        message_count: usize,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        estimated_tokens: Option<u64>,
        trigger: MemoryCompactTrigger,
        configured_threshold: u64,
        effective_threshold: u64,
        microcompact_threshold: u64,
        model_context_window: u64,
        model_max_output_tokens: Option<u64>,
        reserved_output_tokens: u64,
        reserved_output_source: ReservedOutputSource,
        autocompact_buffer_tokens: u64,
    },
    MemoryCompactCompleted {
        before_count: usize,
        after_count: usize,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        summary_tokens: Option<u64>,
        mode: MemoryCompactMode,
        changed: bool,
    },
    SubRunStarted {
        parent_tool_call_id: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        child_session_id: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        task_id: Option<String>,
    },
    SubRunCompleted {
        parent_tool_call_id: String,
        status: AgentStatus,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        final_output: Option<String>,
    },
    HandoffStarted {
        source_agent: String,
        target_agent: String,
        tool_call_id: String,
    },
    HandoffCompleted {
        source_agent: String,
        target_agent: String,
        tool_call_id: String,
    },
    SessionPersisted,
    BudgetSnapshot {
        enforcement_boundary: BudgetEnforcementBoundary,
        budget_usage: BudgetUsageSnapshot,
    },
    BudgetExhausted {
        enforcement_boundary: BudgetEnforcementBoundary,
        budget_usage: BudgetUsageSnapshot,
        budget_exhaustion: BudgetExhaustion,
    },
    CheckpointCreated {
        checkpoint_key: String,
        resume_attempt: u64,
    },
    CheckpointResumed {
        checkpoint_key: String,
        resume_attempt: u64,
    },
    OperationReplayed {
        checkpoint_key: String,
        operation_id: String,
        operation_kind: OperationKind,
        receipt_state: OperationState,
    },
    OperationAmbiguous {
        checkpoint_key: String,
        operation_id: String,
        operation_kind: OperationKind,
        risk: String,
        idempotency_support: Option<ToolIdempotency>,
    },
    ReconciliationRequired {
        checkpoint_key: String,
        operation_id: String,
        operation_kind: OperationKind,
        interruption_reason: String,
        resume_observation: ResumeObservation,
    },
    ModelRetryDuplicateRisk {
        checkpoint_key: String,
        operation_id: String,
        operation_kind: OperationKind,
        risk: String,
    },
    ReconciliationResolved {
        checkpoint_key: String,
        operation_id: String,
        operation_kind: OperationKind,
        decision: ReconciliationDecisionKind,
    },
    RunCompleted {
        status: AgentStatus,
    },
    RunFailed {
        error: String,
    },
    RunCancelled {
        reason: String,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MemoryCompactTrigger {
    MicroThreshold,
    FullThreshold,
    PromptTooLong,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MemoryCompactMode {
    None,
    Micro,
    Structural,
    Summary,
    Emergency,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReservedOutputSource {
    ModelSettings,
    TaskMetadata,
    FrameworkFallback,
    FrameworkFallbackCappedByModelCapability,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModelCallFailureOutcome {
    Definitive,
    Ambiguous,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DiagnosticLevel {
    Debug,
    Info,
    Warning,
    Error,
}

impl DiagnosticLevel {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Debug => "debug",
            Self::Info => "info",
            Self::Warning => "warning",
            Self::Error => "error",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ApprovalAction {
    Allow,
    AllowSession,
    Deny,
    Timeout,
}

impl ApprovalAction {
    pub fn parse(value: &str) -> Option<Self> {
        match value {
            "allow" => Some(Self::Allow),
            "allow_session" => Some(Self::AllowSession),
            "deny" => Some(Self::Deny),
            "timeout" => Some(Self::Timeout),
            _ => None,
        }
    }

    pub fn as_str(self) -> &'static str {
        match self {
            Self::Allow => "allow",
            Self::AllowSession => "allow_session",
            Self::Deny => "deny",
            Self::Timeout => "timeout",
        }
    }

    pub fn is_approved(self) -> bool {
        matches!(self, Self::Allow | Self::AllowSession)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolStatus {
    Started,
    Success,
    Error,
    WaitResponse,
    Running,
    PendingCompress,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgentErrorPayload {
    pub message: String,
    pub code: Option<String>,
}

impl AgentErrorPayload {
    pub fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
            code: None,
        }
    }
}