use super::*;
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_messages(session_id: string) -> list",
category = "agent.host",
runtime_only = true
)]
fn host_agent_session_messages_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(|v| v.display()).unwrap_or_default();
let snapshot = crate::agent_sessions::transcript(&session_id);
let messages = snapshot
.as_ref()
.and_then(|v| dict_get(v, "messages"))
.cloned()
.unwrap_or_else(|| VmValue::List(std::sync::Arc::new(Vec::new())));
Ok(messages)
}
pub(super) fn assistant_message_from_llm_result(llm_result: &VmValue) -> VmValue {
let text = dict_get(llm_result, "text")
.map(|v| v.display())
.unwrap_or_default();
let provider = dict_get(llm_result, "provider")
.map(|v| v.display())
.unwrap_or_default();
let model = dict_get(llm_result, "model")
.map(|v| v.display())
.unwrap_or_default();
let native_calls_value = dict_get(llm_result, "native_tool_calls")
.cloned()
.unwrap_or(VmValue::Nil);
let native_calls_json = list_items(&native_calls_value)
.iter()
.map(vm_to_json)
.collect::<Vec<_>>();
let durable_blocks = durable_anthropic_blocks(llm_result, &provider, &model);
let thinking = dict_get(llm_result, "thinking").map(|v| v.display());
if let Some(message) = assistant_messages::text_channel_provider_surprise_message(
llm_result,
&provider,
&model,
&text,
&native_calls_json,
thinking.as_deref(),
) {
return crate::llm::pairing_receipts::attach_assistant_facts(message, llm_result);
}
if native_calls_json.is_empty() {
if assistant_messages::supports_native_history(llm_result, &provider, &model) {
let recovered_calls = list_items(
&dict_get(llm_result, "tool_calls")
.cloned()
.unwrap_or(VmValue::Nil),
)
.iter()
.map(vm_to_json)
.collect::<Vec<_>>();
if !recovered_calls.is_empty() {
let reasoning = thinking.as_deref().filter(|t| !t.is_empty());
let msg = build_assistant_response_message("", &[], &recovered_calls, reasoning);
return crate::llm::pairing_receipts::attach_assistant_facts(
json_to_vm(&msg),
llm_result,
);
}
}
let mut msg = crate::value::DictMap::new();
if !durable_blocks.is_empty() {
let message =
build_assistant_response_message(&text, &durable_blocks, &[], thinking.as_deref());
return crate::llm::pairing_receipts::attach_assistant_facts(
json_to_vm(&message),
llm_result,
);
}
msg.put_str("role", "assistant");
msg.put_str("content", text);
return crate::llm::pairing_receipts::attach_assistant_facts(
VmValue::dict(msg),
llm_result,
);
}
let msg = build_assistant_response_message(
&text,
&durable_blocks,
&native_calls_json,
thinking.as_deref(),
);
crate::llm::pairing_receipts::attach_assistant_facts(json_to_vm(&msg), llm_result)
}
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_record_assistant(session_id: string, llm_result: dict) -> nil",
category = "agent.host",
runtime_only = true
)]
pub(super) fn host_agent_session_record_assistant_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(|v| v.display()).unwrap_or_default();
let llm_result = args.get(1).cloned().unwrap_or(VmValue::Nil);
let provider = dict_get(&llm_result, "provider")
.map(|v| v.display())
.unwrap_or_default();
let model = dict_get(&llm_result, "model")
.map(|v| v.display())
.unwrap_or_default();
let effective_tool_format = dict_get(&llm_result, "_effective_tool_format")
.map(|v| v.display())
.filter(|format| !format.trim().is_empty());
let raw_tool_calls = dict_get(&llm_result, "tool_calls")
.cloned()
.unwrap_or(VmValue::Nil);
let calls_json = list_items(&raw_tool_calls)
.iter()
.map(vm_to_json)
.collect::<Vec<_>>();
crate::agent_sessions::inject_message(
&session_id,
assistant_message_from_llm_result(&llm_result),
)
.map_err(VmError::Runtime)?;
assistant_messages::record_dispatch_receipt(
&session_id,
calls_json,
provider,
model,
effective_tool_format,
);
Ok(VmValue::Nil)
}
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_pop_last_assistant(session_id: string) -> dict",
category = "agent.host",
runtime_only = true
)]
fn host_agent_session_pop_last_assistant_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(|v| v.display()).unwrap_or_default();
let popped =
crate::agent_sessions::pop_last_if_assistant(&session_id).map_err(VmError::Runtime)?;
Ok(VmValue::Bool(popped))
}
pub(super) fn trailing_assistant_has_native_tool_use(session_id: &str) -> bool {
let Some(transcript) = crate::agent_sessions::transcript(session_id) else {
return false;
};
let messages = list_items(
&dict_get(&transcript, "messages")
.cloned()
.unwrap_or(VmValue::Nil),
);
let Some(last) = messages.last() else {
return false;
};
if dict_get(last, "role")
.map(|v| v.display())
.unwrap_or_default()
!= "assistant"
{
return false;
}
!assistant_tool_use_blocks(last).is_empty()
}
pub(super) fn pair_orphaned_tool_use(session_id: &str, feedback: &str) -> usize {
let Some(transcript) = crate::agent_sessions::transcript(session_id) else {
return 0;
};
let messages = list_items(
&dict_get(&transcript, "messages")
.cloned()
.unwrap_or(VmValue::Nil),
);
let Some(last) = messages.last() else {
return 0;
};
let role = dict_get(last, "role")
.map(|v| v.display())
.unwrap_or_default();
if role != "assistant" {
return 0;
}
let already_paired = paired_tool_result_ids(&messages);
let synthetic = synthesize_orphan_tool_results(last, feedback, &already_paired);
let mut repaired = 0;
for message in synthetic {
if crate::agent_sessions::inject_message(session_id, message).is_ok() {
repaired += 1;
}
}
repaired
}
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_pair_orphaned_tool_use(session_id: string, feedback: string) -> int",
category = "agent.host",
runtime_only = true
)]
fn host_agent_session_pair_orphaned_tool_use_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(|v| v.display()).unwrap_or_default();
let feedback = args.get(1).map(|v| v.display()).unwrap_or_default();
let repaired = pair_orphaned_tool_use(&session_id, &feedback);
Ok(VmValue::Int(repaired as i64))
}
const MESSAGE_HISTORY_BUILTINS: &[&VmBuiltinDef] = &[
&HOST_AGENT_SESSION_MESSAGES_BUILTIN_DEF,
&HOST_AGENT_SESSION_RECORD_ASSISTANT_BUILTIN_DEF,
&HOST_AGENT_SESSION_POP_LAST_ASSISTANT_BUILTIN_DEF,
&HOST_AGENT_SESSION_PAIR_ORPHANED_TOOL_USE_BUILTIN_DEF,
];
pub(super) fn register_message_history_primitives(vm: &mut Vm) {
register_builtin_defs(vm, MESSAGE_HISTORY_BUILTINS);
}