vv-agent 0.8.0

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use std::collections::BTreeMap;

use serde_json::Value;

use crate::events::{RunEvent, RunEventPayload};

use super::RuntimeEventContext;

const JSON_SAFE_INTEGER_MAX: u64 = (1_u64 << 53) - 1;

pub(crate) fn map_stream_event(
    payload: &BTreeMap<String, Value>,
    context: &RuntimeEventContext,
) -> Option<RunEvent> {
    let event = payload.get("event").and_then(Value::as_str)?;
    let cycle_index = positive_cycle(payload, "cycle")?;
    let projected = project_stream_payload(event, payload)?;
    Some(context.attach(RunEvent::new(
        &context.run_id,
        &context.trace_id,
        &context.agent_name,
        Some(cycle_index),
        projected,
    )))
}

fn project_stream_payload(
    event: &str,
    payload: &BTreeMap<String, Value>,
) -> Option<RunEventPayload> {
    match event {
        "assistant_delta" => Some(RunEventPayload::AssistantDelta {
            delta: payload
                .get("content_delta")
                .and_then(Value::as_str)
                .or_else(|| payload.get("delta").and_then(Value::as_str))?
                .to_string(),
            content_chars: optional_counter(payload, "content_chars")?,
            estimated_tokens: optional_counter(payload, "estimated_tokens")?,
        }),
        "reasoning_delta" => Some(RunEventPayload::ReasoningDelta {
            delta: payload.get("reasoning_delta")?.as_str()?.to_string(),
            reasoning_chars: optional_counter(payload, "reasoning_chars")?,
            estimated_tokens: optional_counter(payload, "estimated_tokens")?,
        }),
        "tool_call_started" => Some(RunEventPayload::ModelToolCallStarted {
            tool_call_id: required_text(payload, "tool_call_id")?.to_string(),
            tool_call_index: optional_counter(payload, "tool_call_index")?,
            tool_name: required_text(payload, "function_name")?.to_string(),
            arguments_chars: optional_counter(payload, "arguments_chars")?,
            estimated_tokens: optional_counter(payload, "estimated_tokens")?,
        }),
        "tool_call_progress" => Some(RunEventPayload::ModelToolCallProgress {
            tool_call_id: required_text(payload, "tool_call_id")?.to_string(),
            tool_call_index: optional_counter(payload, "tool_call_index")?,
            tool_name: required_text(payload, "function_name")?.to_string(),
            arguments_chars: optional_counter(payload, "arguments_chars")?,
            estimated_tokens: optional_counter(payload, "estimated_tokens")?,
        }),
        _ => None,
    }
}

fn required_text<'a>(payload: &'a BTreeMap<String, Value>, key: &str) -> Option<&'a str> {
    payload
        .get(key)
        .and_then(Value::as_str)
        .filter(|value| !value.trim().is_empty())
}

fn positive_cycle(payload: &BTreeMap<String, Value>, key: &str) -> Option<u32> {
    let value = payload.get(key)?.as_u64()?;
    u32::try_from(value).ok().filter(|value| *value > 0)
}

fn optional_counter(payload: &BTreeMap<String, Value>, key: &str) -> Option<Option<u64>> {
    match payload.get(key) {
        None | Some(Value::Null) => Some(None),
        Some(value) => value
            .as_u64()
            .filter(|value| *value <= JSON_SAFE_INTEGER_MAX)
            .map(Some),
    }
}