harn-vm 0.10.89

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

use super::{dict_get, json_to_vm, list_items, vm_to_json};
use crate::llm::tools::{
    assistant_prose_block, build_assistant_response_message, render_canonical_call,
    text_tool_call_block,
};

pub(super) fn effective_session_tool_format(
    provider: &str,
    model: &str,
    requested: &str,
) -> String {
    // Mock/replay fixtures deliberately bypass live capability admission so
    // they can exercise every wire shape against one deterministic provider.
    if crate::llm::providers::is_internal_simulator(provider) {
        return requested.to_string();
    }
    let caps = crate::llm::managed_supply::capabilities_for(provider, model);
    crate::llm::capabilities::validate_tool_format_with_caps(provider, model, requested, &caps)
        .effective
}

pub(super) fn effective_history_tool_format(
    llm_result: &VmValue,
    provider: &str,
    model: &str,
) -> String {
    // Legacy/replay fixtures without private route metadata historically mean
    // native structured history. Live results carry the admitted format; the
    // capability decision still repairs a missing-field Fireworks surprise.
    let requested = dict_get(llm_result, "_agent_tool_format")
        .map(|value| value.display())
        .filter(|format| !format.trim().is_empty())
        .unwrap_or_else(|| "native".to_string());
    effective_session_tool_format(provider, model, &requested)
}

pub(super) fn supports_native_history(llm_result: &VmValue, provider: &str, model: &str) -> bool {
    effective_history_tool_format(llm_result, provider, model) == "native"
        && crate::llm::managed_supply::capabilities_for(provider, model).native_tools
}

pub(super) fn durable_anthropic_blocks(
    llm_result: &VmValue,
    provider: &str,
    model: &str,
) -> Vec<serde_json::Value> {
    let blocks = dict_get(llm_result, "blocks")
        .map(list_items)
        .unwrap_or_default()
        .iter()
        .map(vm_to_json)
        .collect::<Vec<_>>();
    crate::llm::reasoning_history::capture_anthropic_blocks(
        &blocks,
        crate::llm::provider::provider_uses_anthropic_messages(provider, model),
    )
}

pub(super) fn canonical_text_history_for_tool_calls(
    text: &str,
    tool_calls: &[serde_json::Value],
    tool_format: &str,
) -> Option<String> {
    let mut parts = Vec::new();
    let trimmed = text.trim();
    if !trimmed.is_empty() {
        parts.push(if tool_format == "json" {
            trimmed.to_string()
        } else {
            assistant_prose_block(trimmed)
        });
    }
    for call in tool_calls {
        let name = call
            .get("name")
            .and_then(serde_json::Value::as_str)
            .unwrap_or("")
            .trim();
        if name.is_empty() {
            continue;
        }
        let args = call
            .get("arguments")
            .cloned()
            .unwrap_or_else(|| serde_json::json!({}));
        parts.push(if tool_format == "json" {
            let call = serde_json::json!({"name": name, "args": args});
            format!(
                "```tool\n{}\n```",
                serde_json::to_string_pretty(&call)
                    .expect("JSON tool-history values must serialize")
            )
        } else {
            text_tool_call_block(&render_canonical_call(name, &args))
        });
    }
    (!parts.is_empty()).then(|| parts.join("\n\n"))
}

pub(super) fn text_channel_provider_surprise_message(
    llm_result: &VmValue,
    provider: &str,
    model: &str,
    text: &str,
    tool_calls: &[serde_json::Value],
    thinking: Option<&str>,
) -> Option<VmValue> {
    let format = effective_history_tool_format(llm_result, provider, model);
    if tool_calls.is_empty()
        || crate::llm_config::tool_format_channel(&format)
            != Some(crate::llm_config::ToolFormatChannel::Text)
    {
        return None;
    }
    let history = canonical_text_history_for_tool_calls(text, tool_calls, &format)?;
    Some(json_to_vm(&build_assistant_response_message(
        &history,
        &[],
        &[],
        thinking,
        provider,
        model,
    )))
}