vv-agent 0.7.2

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use super::*;

#[test]
fn runtime_retries_prompt_too_long_with_emergency_compaction() {
    let llm = PromptTooLongRetryLlmClient::default();
    let inspector = llm.clone();
    let runtime = AgentRuntime::new(llm);
    let mut task = AgentTask::new("ptl_task", "demo", "system", "finish after retry");
    task.no_tool_policy = vv_agent::NoToolPolicy::Finish;
    task.memory_compact_threshold = 10_000;
    task.metadata
        .insert("model_context_window".to_string(), json!(20_000));
    task.metadata
        .insert("reserved_output_tokens".to_string(), json!(0));
    task.metadata
        .insert("autocompact_buffer_tokens".to_string(), json!(0));
    task.metadata
        .insert("memory_keep_recent_messages".to_string(), json!(2));

    let result = runtime.run(task).expect("run");

    assert_eq!(result.status, AgentStatus::Completed);
    assert_eq!(result.final_answer.as_deref(), Some("done after retry"));
    assert!(result.cycles.iter().any(|cycle| cycle.memory_compacted));
    assert_eq!(inspector.request_count(), 3);
    let final_request = inspector.final_request_messages();
    assert!(
        final_request.len() < 4,
        "final retry should use emergency-compacted messages: {final_request:#?}"
    );
}

#[test]
fn runtime_returns_compaction_exhausted_after_prompt_too_long_retries() {
    let llm = AlwaysPromptTooLongLlmClient::default();
    let inspector = llm.clone();
    let runtime = AgentRuntime::new(llm);
    let mut task = AgentTask::new("ptl_exhausted", "demo", "system", "never fits");
    task.memory_compact_threshold = 10_000;
    task.metadata
        .insert("model_context_window".to_string(), json!(20_000));
    task.metadata
        .insert("reserved_output_tokens".to_string(), json!(0));
    task.metadata
        .insert("autocompact_buffer_tokens".to_string(), json!(0));

    let error = runtime.run(task).expect_err("compaction exhausted error");

    match error {
        LlmError::CompactionExhausted(error) => {
            assert_eq!(error.attempts, 4);
            assert!(error
                .last_error
                .as_deref()
                .is_some_and(|message| message.contains("Prompt is too long")));
        }
        other => panic!("expected CompactionExhausted, got {other:?}"),
    }
    assert_eq!(inspector.request_count(), 4);
}
#[derive(Clone, Default)]
struct PromptTooLongRetryLlmClient {
    requests_seen: Arc<Mutex<usize>>,
    final_request_messages: Arc<Mutex<Vec<Message>>>,
}

impl PromptTooLongRetryLlmClient {
    fn request_count(&self) -> usize {
        *self.requests_seen.lock().expect("request count poisoned")
    }

    fn final_request_messages(&self) -> Vec<Message> {
        self.final_request_messages
            .lock()
            .expect("messages poisoned")
            .clone()
    }
}

impl LlmClient for PromptTooLongRetryLlmClient {
    fn complete(&self, request: LlmRequest) -> Result<LLMResponse, LlmError> {
        let mut requests_seen = self
            .requests_seen
            .lock()
            .map_err(|_| LlmError::Request("request count poisoned".to_string()))?;
        *requests_seen += 1;
        if *requests_seen <= 2 {
            return Err(LlmError::Request(
                "Prompt is too long for this model".to_string(),
            ));
        }
        *self
            .final_request_messages
            .lock()
            .expect("messages poisoned") = request.messages;
        Ok(LLMResponse::new("done after retry"))
    }
}

#[derive(Clone, Default)]
struct AlwaysPromptTooLongLlmClient {
    requests_seen: Arc<Mutex<usize>>,
}

impl AlwaysPromptTooLongLlmClient {
    fn request_count(&self) -> usize {
        *self.requests_seen.lock().expect("request count poisoned")
    }
}

impl LlmClient for AlwaysPromptTooLongLlmClient {
    fn complete(&self, _request: LlmRequest) -> Result<LLMResponse, LlmError> {
        let mut requests_seen = self
            .requests_seen
            .lock()
            .map_err(|_| LlmError::Request("request count poisoned".to_string()))?;
        *requests_seen += 1;
        Err(LlmError::Request(
            "Prompt is too long for this model".to_string(),
        ))
    }
}