harn-vm 0.10.116

Async bytecode virtual machine for the Harn programming language
Documentation
use super::host_agent_parse_tool_calls_impl;
use crate::value::VmValue;

fn vm_str(value: &str) -> VmValue {
    VmValue::String(arcstr::ArcStr::from(value))
}

fn look_tool_catalog() -> VmValue {
    crate::stdlib::json_to_vm_value(&serde_json::json!({
        "tools": [
            {
                "name": "look",
                "description": "Read a file",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "file": { "type": "string" },
                        "intent": { "type": "string" }
                    },
                    "required": ["file", "intent"]
                }
            }
        ]
    }))
}

fn parse_ids(text: &str) -> Vec<String> {
    let mut vm = crate::vm::Vm::new();
    crate::register_core_stdlib(&mut vm);
    crate::stdlib::macros::register_builtin_defs(
        &mut vm,
        crate::llm::tools::PARSE_HOST_PRIMITIVE_BUILTINS,
    );
    let value = futures::executor::block_on(host_agent_parse_tool_calls_impl(
        crate::vm::AsyncBuiltinCtx::for_test(vm),
        vec![vm_str(text), look_tool_catalog(), vm_str("text")],
    ))
    .expect("parse primitive succeeds");
    let json = crate::llm::helpers::vm_value_to_json(&value);
    json.get("calls")
        .and_then(|calls| calls.as_array())
        .expect("calls array")
        .iter()
        .map(|call| {
            call.get("id")
                .and_then(|id| id.as_str())
                .expect("call id")
                .to_string()
        })
        .collect()
}

#[test]
fn parse_tool_call_ids_are_session_scoped_across_turns() {
    crate::agent_sessions::reset_session_store();
    let session_a = crate::agent_sessions::open_or_create(Some("parse-id-a".to_string()));
    let session_b = crate::agent_sessions::open_or_create(Some("parse-id-b".to_string()));
    let text = "<tool_call>\nlook({ file: \"Cargo.toml\", intent: \"read\" })\n</tool_call>";

    {
        let _guard = crate::agent_sessions::enter_current_session(session_a.clone());
        assert_eq!(parse_ids(text), vec!["tc_0"]);
        assert_eq!(parse_ids(text), vec!["tc_1"]);
    }

    {
        let _guard = crate::agent_sessions::enter_current_session(session_b);
        assert_eq!(parse_ids(text), vec!["tc_0"]);
    }

    {
        let _guard = crate::agent_sessions::enter_current_session(session_a);
        assert_eq!(parse_ids(text), vec!["tc_2"]);
    }
}

#[test]
fn parse_tool_call_ids_are_unique_within_one_turn() {
    crate::agent_sessions::reset_session_store();
    let session = crate::agent_sessions::open_or_create(Some("parse-id-batch".to_string()));
    let _guard = crate::agent_sessions::enter_current_session(session);
    let text = [
        "<tool_call>\nlook({ file: \"Cargo.toml\", intent: \"read\" })\n</tool_call>",
        "<tool_call>\nlook({ file: \"README.md\", intent: \"read\" })\n</tool_call>",
    ]
    .join("\n");

    assert_eq!(parse_ids(&text), vec!["tc_0", "tc_1"]);
}

#[test]
fn parse_tool_call_ids_continue_after_seeded_transcript() {
    crate::agent_sessions::reset_session_store();
    let session = crate::agent_sessions::seed_from_messages(
        Some("parse-id-seeded".to_string()),
        &[
            serde_json::json!({
                "role": "assistant",
                "content": "",
                "tool_calls": [
                    {
                        "id": "tc_5",
                        "type": "function",
                        "function": {
                            "name": "look",
                            "arguments": "{\"file\":\"Cargo.toml\",\"intent\":\"read\"}"
                        }
                    }
                ]
            }),
            serde_json::json!({
                "role": "tool",
                "tool_call_id": "tc_5",
                "content": "{}"
            }),
        ],
        serde_json::json!({}),
        None,
        Some("text".to_string()),
    )
    .expect("seed session");
    let _guard = crate::agent_sessions::enter_current_session(session);
    let text = "<tool_call>\nlook({ file: \"README.md\", intent: \"read\" })\n</tool_call>";

    assert_eq!(parse_ids(text), vec!["tc_6"]);
}