harn-vm 0.10.24

Async bytecode virtual machine for the Harn programming language
Documentation
use super::AnthropicProvider;
use crate::llm::api::{LlmCallOptions, LlmRequestPayload};

#[test]
fn mid_conversation_system_section_reaches_exact_anthropic_wire_json() {
    let opts = LlmCallOptions {
        provider: "anthropic".to_string(),
        model: "claude-opus-4-8".to_string(),
        messages: vec![
            serde_json::json!({"role": "user", "content": "U1 <>&"}),
            serde_json::json!({
                "role": "system",
                "content": [{
                    "type": "text",
                    "text": "first <>&",
                    "cache_control": {"type": "ephemeral"}
                }]
            }),
            serde_json::json!({
                "role": "developer",
                "content": [{"type": "text", "text": "second"}]
            }),
            serde_json::json!({"role": "assistant", "content": "A1"}),
        ],
        max_tokens: 64,
        ..LlmCallOptions::default()
    };

    let payload = LlmRequestPayload::from(&opts);
    let body = AnthropicProvider::build_request_body(&payload);

    assert_eq!(
        body["messages"],
        serde_json::json!([
            {"role": "user", "content": "U1 <>&"},
            {
                "role": "system",
                "content": [
                    {
                        "type": "text",
                        "text": "first <>&",
                        "cache_control": {"type": "ephemeral"}
                    },
                    {"type": "text", "text": "second"}
                ]
            },
            {"role": "assistant", "content": "A1"},
        ])
    );
}

#[test]
fn exact_server_tool_use_boundary_keeps_native_system_message() {
    let server_tool_use = serde_json::json!({
        "type": "server_tool_use",
        "id": "srvtoolu_01",
        "name": "web_search",
        "input": {"query": "Harn"}
    });
    let opts = LlmCallOptions {
        provider: "anthropic".to_string(),
        model: "claude-opus-4-8".to_string(),
        messages: vec![
            serde_json::json!({"role": "user", "content": "search"}),
            serde_json::json!({"role": "assistant", "content": [server_tool_use]}),
            serde_json::json!({"role": "system", "content": "budget is now $0.25"}),
            serde_json::json!({"role": "assistant", "content": "continuing"}),
        ],
        max_tokens: 64,
        ..LlmCallOptions::default()
    };

    let payload = LlmRequestPayload::from(&opts);
    let body = AnthropicProvider::build_request_body(&payload);

    assert_eq!(
        body["messages"],
        serde_json::json!([
            {"role": "user", "content": "search"},
            {"role": "assistant", "content": [{
                "type": "server_tool_use",
                "id": "srvtoolu_01",
                "name": "web_search",
                "input": {"query": "Harn"}
            }]},
            {"role": "system", "content": "budget is now $0.25"},
            {"role": "assistant", "content": "continuing"},
        ])
    );
}

fn assert_mixed_tool_boundary_moves_system_after_client_result(blocks: Vec<serde_json::Value>) {
    let expected_blocks = blocks.clone();
    let opts = LlmCallOptions {
        provider: "anthropic".to_string(),
        model: "claude-opus-4-8".to_string(),
        messages: vec![
            serde_json::json!({"role": "user", "content": "search and read"}),
            serde_json::json!({"role": "assistant", "content": blocks}),
            serde_json::json!({"role": "system", "content": "budget is now $0.25"}),
            serde_json::json!({"role": "user", "content": [{
                "type": "tool_result", "tool_use_id": "toolu_01", "content": "contents"
            }]}),
            serde_json::json!({"role": "assistant", "content": "continuing"}),
        ],
        max_tokens: 64,
        ..LlmCallOptions::default()
    };

    let payload = LlmRequestPayload::from(&opts);
    let body = AnthropicProvider::build_request_body(&payload);
    assert_eq!(
        body["messages"],
        serde_json::json!([
            {"role": "user", "content": "search and read"},
            {"role": "assistant", "content": expected_blocks},
            {"role": "user", "content": [{
                "type": "tool_result", "tool_use_id": "toolu_01", "content": "contents"
            }]},
            {"role": "system", "content": "budget is now $0.25"},
            {"role": "assistant", "content": "continuing"},
        ])
    );
}

#[test]
fn mixed_client_then_server_tool_use_defers_native_system_until_after_result() {
    assert_mixed_tool_boundary_moves_system_after_client_result(vec![
        serde_json::json!({
            "type": "tool_use", "id": "toolu_01", "name": "read_file", "input": {"path": "README.md"}
        }),
        serde_json::json!({
            "type": "server_tool_use", "id": "srvtoolu_01", "name": "web_search", "input": {"query": "Harn"}
        }),
    ]);
}

#[test]
fn mixed_server_then_client_tool_use_defers_native_system_until_after_result() {
    assert_mixed_tool_boundary_moves_system_after_client_result(vec![
        serde_json::json!({
            "type": "server_tool_use", "id": "srvtoolu_01", "name": "web_search", "input": {"query": "Harn"}
        }),
        serde_json::json!({
            "type": "tool_use", "id": "toolu_01", "name": "read_file", "input": {"path": "README.md"}
        }),
    ]);
}

#[test]
fn mixed_tool_fold_keeps_result_only_and_moves_reminder_after_continuation() {
    let blocks = vec![
        serde_json::json!({
            "type": "tool_use", "id": "toolu_01", "name": "read_file", "input": {"path": "README.md"}
        }),
        serde_json::json!({
            "type": "server_tool_use", "id": "srvtoolu_01", "name": "web_search", "input": {"query": "Harn"}
        }),
    ];
    let expected_blocks = blocks.clone();
    let opts = LlmCallOptions {
        provider: "anthropic".to_string(),
        model: "claude-fable-5".to_string(),
        messages: vec![
            serde_json::json!({"role": "user", "content": "search and read"}),
            serde_json::json!({"role": "assistant", "content": blocks}),
            serde_json::json!({"role": "system", "content": "budget is now $0.25"}),
            serde_json::json!({"role": "user", "content": [{
                "type": "tool_result", "tool_use_id": "toolu_01", "content": "contents"
            }]}),
            serde_json::json!({"role": "assistant", "content": "continuing"}),
        ],
        max_tokens: 64,
        ..LlmCallOptions::default()
    };

    let payload = LlmRequestPayload::from(&opts);
    let body = AnthropicProvider::build_request_body(&payload);
    assert_eq!(
        body["messages"],
        serde_json::json!([
            {"role": "user", "content": "search and read"},
            {"role": "assistant", "content": expected_blocks},
            {"role": "user", "content": [{
                "type": "tool_result", "tool_use_id": "toolu_01", "content": "contents"
            }]},
            {"role": "assistant", "content": "continuing"},
            {"role": "user", "content": "<system-reminder>\nbudget is now $0.25\n</system-reminder>"},
        ])
    );
}

#[test]
fn unsupported_fable_route_folds_system_message_on_exact_wire() {
    let opts = LlmCallOptions {
        provider: "anthropic".to_string(),
        model: "claude-fable-5".to_string(),
        messages: vec![
            serde_json::json!({"role": "user", "content": "U1"}),
            serde_json::json!({"role": "system", "content": "operator constraint"}),
            serde_json::json!({"role": "assistant", "content": "A1"}),
        ],
        max_tokens: 64,
        ..LlmCallOptions::default()
    };

    let payload = LlmRequestPayload::from(&opts);
    let body = AnthropicProvider::build_request_body(&payload);

    assert_eq!(
        body["messages"],
        serde_json::json!([
            {
                "role": "user",
                "content": "U1\n\n<system-reminder>\noperator constraint\n</system-reminder>"
            },
            {"role": "assistant", "content": "A1"},
        ])
    );
}