starweaver-runtime 0.10.0

Agent-loop graph and runtime executor primitives for Starweaver
Documentation
#![allow(missing_docs, clippy::unwrap_used)]

use std::sync::Arc;

use serde::Deserialize;
use starweaver_model::{ModelResponse, ModelResponsePart, TestModel};
use starweaver_runtime::{Agent, AgentError, OutputMedia, OutputPolicy, OutputSchema, OutputValue};

#[derive(Debug, Deserialize, PartialEq)]
struct Answer {
    answer: String,
}

#[tokio::test]
async fn agent_result_parses_structured_output_into_type() {
    let agent = Agent::new(Arc::new(TestModel::with_json(&serde_json::json!({
        "answer": "Paris"
    }))))
    .with_output_schema(OutputSchema::new(
        "answer",
        serde_json::json!({
            "type": "object",
            "required": ["answer"]
        }),
    ));

    let result = agent.run("answer").await.unwrap();
    let parsed: Answer = result.structured().unwrap();

    assert_eq!(parsed.answer, "Paris");
}

#[tokio::test]
async fn agent_result_reports_missing_structured_output() {
    let result = Agent::new(Arc::new(TestModel::with_text("plain")))
        .run("answer")
        .await
        .unwrap();

    let error = result.structured::<Answer>().unwrap_err();

    assert!(
        matches!(error, AgentError::StructuredOutput(message) if message == "missing structured output")
    );
}

#[test]
fn output_value_parses_text_or_json_into_type() {
    let from_text: Answer = OutputValue::Text(r#"{"answer":"Paris"}"#.to_string())
        .parse()
        .unwrap();
    let from_json: Answer = OutputValue::Json(serde_json::json!({"answer": "Paris"}))
        .parse()
        .unwrap();

    assert_eq!(from_text, from_json);
}

#[tokio::test]
async fn agent_result_exposes_image_output_wrappers() {
    let mut response = ModelResponse::text("generated image");
    response.parts.push(ModelResponsePart::File {
        url: "resource://generated/image-1".to_string(),
        media_type: "image/png".to_string(),
    });

    let result = Agent::new(Arc::new(TestModel::with_responses(vec![response])))
        .with_output_policy(OutputPolicy::image())
        .run("draw")
        .await
        .unwrap();

    assert_eq!(result.output, "generated image");
    assert_eq!(
        result.image_outputs(),
        vec![OutputMedia::new(
            "resource://generated/image-1",
            "image/png"
        )]
    );
    assert_eq!(result.media_outputs(), result.image_outputs());
    assert_eq!(
        result.output_value(),
        OutputValue::Media(vec![OutputMedia::new(
            "resource://generated/image-1",
            "image/png"
        )])
    );
    let parsed: Vec<OutputMedia> = result.output_value().parse().unwrap();
    assert!(parsed[0].is_image());
}