atman-runtime 1.4.0

atman flow execution runtime: evaluator, tool dispatch, provider dispatch, executor, memory stores
Documentation
use atman_dsl::parse::parse_file;
use atman_runtime::message::{ImageData, MessagePart, MessageRole};
use atman_runtime::{Executor, Value};

#[tokio::test]
async fn user_msg_positional_text_produces_message_value() {
    let src = r#"flow build() -> Message {
    return user_msg("hello")
}"#;
    let file = parse_file(src).unwrap();
    let ex = Executor::new();
    let out = ex.run(&file, "build", vec![]).await.unwrap();
    let Value::Message(msg) = out else {
        panic!("expected Value::Message, got {out:?}");
    };
    assert_eq!(msg.role, MessageRole::User);
    assert_eq!(msg.parts.len(), 1);
    assert!(matches!(&msg.parts[0], MessagePart::Text { text } if text == "hello"));
}

#[tokio::test]
async fn user_msg_with_attachments_prepends_image_parts() {
    let src = r#"flow build() -> Message {
    return user_msg("describe", attachments: [@"a.png"])
}"#;
    let file = parse_file(src).unwrap();
    let ex = Executor::new();
    let out = ex.run(&file, "build", vec![]).await.unwrap();
    let Value::Message(msg) = out else {
        panic!("expected message");
    };
    assert_eq!(msg.parts.len(), 2);
    let MessagePart::Image { source } = &msg.parts[0] else {
        panic!("first part should be image, got {:?}", msg.parts[0]);
    };
    assert_eq!(source.media_type, "image/png");
    assert!(
        matches!(&source.data, ImageData::Path { path } if path.to_string_lossy().ends_with("a.png"))
    );
    assert!(matches!(&msg.parts[1], MessagePart::Text { text } if text == "describe"));
}

#[tokio::test]
async fn tool_result_takes_id_content_and_optional_is_error() {
    let src = r#"flow build() -> Message {
    return tool_result("toolu_x", "output", is_error: true)
}"#;
    let file = parse_file(src).unwrap();
    let ex = Executor::new();
    let out = ex.run(&file, "build", vec![]).await.unwrap();
    let Value::Message(msg) = out else {
        panic!("expected message");
    };
    assert_eq!(msg.role, MessageRole::Tool);
    let MessagePart::ToolResult {
        tool_use_id,
        content,
        is_error,
    } = &msg.parts[0]
    else {
        panic!("expected tool_result part");
    };
    assert_eq!(tool_use_id, "toolu_x");
    assert_eq!(content, "output");
    assert!(*is_error);
}

#[tokio::test]
async fn system_msg_text_only() {
    let src = r#"flow build() -> Message {
    return system_msg("you are a reviewer")
}"#;
    let file = parse_file(src).unwrap();
    let ex = Executor::new();
    let out = ex.run(&file, "build", vec![]).await.unwrap();
    let Value::Message(msg) = out else {
        panic!("expected message");
    };
    assert_eq!(msg.role, MessageRole::System);
    assert!(matches!(&msg.parts[0], MessagePart::Text { text } if text == "you are a reviewer"));
}