use crate::value::{VmError, VmValue};
use crate::vm::Vm;
use harn_builtin_macros::harn_builtin;
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_visible_messages(session_id: string, messages?: list|nil) -> list",
category = "agent.host",
runtime_only = true
)]
fn host_agent_session_visible_messages_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(VmValue::display).unwrap_or_default();
let messages = match args.get(1) {
Some(VmValue::List(messages)) => messages.as_ref().clone(),
_ => durable_messages(&session_id),
};
Ok(VmValue::list(visible_message_values(&session_id, messages)))
}
pub(crate) fn visible_message_values(session_id: &str, messages: Vec<VmValue>) -> Vec<VmValue> {
let reminders = crate::llm::helpers::pending_reminders_from_session(Some(session_id));
let rendered = crate::llm::helpers::render_pending_reminders(
&crate::llm::capabilities::Capabilities::default(),
&reminders,
);
let directive_nonce = crate::llm::helpers::directive_nonce_for_session(session_id);
let source_count = messages.len();
let mut visible = messages;
let pending = crate::llm::helpers::uncommitted_directives(&visible, &rendered);
if let Some(envelope) =
crate::llm::helpers::directive_envelope_message(&pending, &directive_nonce)
{
visible.push(super::json_to_vm(&envelope));
}
let compaction_receipt_ref = latest_compaction_receipt_ref(session_id);
VISIBLE_MESSAGE_MEMO.with(|memo| {
let mut memo = memo.borrow_mut();
for (position, message) in visible.iter_mut().enumerate() {
if message.as_dict().is_none() {
continue;
}
let source_index = (position < source_count).then_some(position);
let input = message.clone();
*message = memo.project(
position,
&input,
(source_index, compaction_receipt_ref.clone()),
|| with_lineage(&input, source_index, compaction_receipt_ref.as_ref()),
|_| 1,
);
}
});
visible
}
type VisibleMessageMemo =
crate::llm::content_hash::IdentitySequenceMemo<(Option<usize>, Option<String>), VmValue>;
thread_local! {
static VISIBLE_MESSAGE_MEMO: std::cell::RefCell<VisibleMessageMemo> = const {
std::cell::RefCell::new(crate::llm::content_hash::IdentitySequenceMemo::new(
VISIBLE_MESSAGE_MEMO_ENTRIES,
))
};
#[cfg(test)]
static VISIBLE_MESSAGE_PROJECTIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
const VISIBLE_MESSAGE_MEMO_ENTRIES: usize = 65_536;
#[cfg(test)]
pub(crate) fn visible_message_projections() -> usize {
VISIBLE_MESSAGE_PROJECTIONS.with(std::cell::Cell::get)
}
fn with_lineage(
message: &VmValue,
source_index: Option<usize>,
compaction_receipt_ref: Option<&String>,
) -> VmValue {
#[cfg(test)]
VISIBLE_MESSAGE_PROJECTIONS.with(|count| count.set(count.get() + 1));
let Some(fields) = message.as_dict() else {
return message.clone();
};
let mut attached = attached_lineage(
fields.get(crate::llm::message_lineage::MESSAGE_LINEAGE_KEY),
source_index,
);
attached.message.semantic_kind = semantic_kind(message);
if attached.message.source_message_index == Some(0) {
attached.message.compaction_receipt_ref = compaction_receipt_ref.cloned();
if attached.message.compaction_receipt_ref.is_some() {
attached.message.semantic_kind =
crate::llm::message_lineage::MessageSemanticKind::CondensedMemory;
}
}
let lineage = super::json_to_vm(
&serde_json::to_value(attached).expect("message lineage is JSON representable"),
);
let mut next = fields.clone();
next.insert(
crate::value::intern_key(crate::llm::message_lineage::MESSAGE_LINEAGE_KEY),
lineage,
);
VmValue::dict_map(next)
}
#[cfg(test)]
pub(crate) fn visible_messages_with_lineage(
session_id: &str,
messages: Vec<serde_json::Value>,
) -> Vec<serde_json::Value> {
let messages = messages.iter().map(super::json_to_vm).collect();
visible_message_values(session_id, messages)
.iter()
.map(super::vm_to_json)
.collect()
}
fn attached_lineage(
metadata: Option<&VmValue>,
source_message_index: Option<usize>,
) -> crate::llm::message_lineage::AttachedMessageLineage {
use crate::llm::message_lineage::{
raw_projection, unknown_projection, AttachedMessageLineage, MessageLineageEntry,
};
match metadata {
None => AttachedMessageLineage {
projection: raw_projection(),
message: MessageLineageEntry {
source_message_index,
..MessageLineageEntry::default()
},
},
Some(metadata) => {
serde_json::from_value(super::vm_to_json(metadata)).unwrap_or_else(|_| {
AttachedMessageLineage {
projection: unknown_projection(),
message: MessageLineageEntry::default(),
}
})
}
}
}
fn semantic_kind(message: &VmValue) -> crate::llm::message_lineage::MessageSemanticKind {
use crate::llm::helpers::MessageView;
use crate::llm::message_lineage::MessageSemanticKind;
if crate::llm::helpers::has_directive_commit_metadata(message) {
return MessageSemanticKind::ContextDirective;
}
match message.str_field("role") {
Some("user") => MessageSemanticKind::User,
Some("assistant")
if message
.list_len_field("tool_calls")
.is_some_and(|calls| calls > 0) =>
{
MessageSemanticKind::AssistantToolCall
}
Some("assistant") => MessageSemanticKind::Assistant,
Some("tool" | "tool_result") => MessageSemanticKind::ToolResult,
Some("system" | "developer") => MessageSemanticKind::Instruction,
_ => MessageSemanticKind::Unknown,
}
}
fn latest_compaction_receipt_ref(session_id: &str) -> Option<String> {
use crate::llm::helpers::MessageView;
let transcript = crate::agent_sessions::transcript(session_id)?;
let transcript = transcript.as_dict()?;
match transcript.get("summary") {
Some(VmValue::String(summary)) if !summary.is_empty() => {}
_ => return None,
}
let VmValue::List(events) = transcript.get("events")? else {
return None;
};
events.iter().rev().find_map(|event| {
if event.str_field("kind") != Some("compaction") {
return None;
}
let receipt = event
.as_dict()?
.get("metadata")?
.as_dict()?
.get("receipt")?;
receipt
.str_field("receipt_id")
.filter(|value| !value.is_empty())
.map(str::to_string)
})
}
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_commit_directives(session_id: string) -> int",
category = "agent.host",
runtime_only = true
)]
fn host_agent_session_commit_directives_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(VmValue::display).unwrap_or_default();
if session_id.is_empty() {
return Ok(VmValue::Int(0));
}
let messages = durable_messages(&session_id);
let reminders = crate::llm::helpers::pending_reminders_from_session(Some(&session_id));
let withdrawal_reason = crate::agent_sessions::last_withdrawal_reason(&session_id);
let pending = crate::llm::helpers::turn_boundary_directives(
&messages,
&reminders,
withdrawal_reason.as_deref(),
);
let nonce = crate::llm::helpers::directive_nonce_for_session(&session_id);
let Some(message) = crate::llm::helpers::directive_envelope_message(&pending, &nonce) else {
return Ok(VmValue::Int(0));
};
crate::agent_sessions::inject_message(&session_id, super::json_to_vm(&message))
.map_err(VmError::Runtime)?;
Ok(VmValue::Int(pending.len() as i64))
}
fn durable_messages(session_id: &str) -> Vec<VmValue> {
match crate::agent_sessions::transcript(session_id)
.as_ref()
.and_then(|value| super::dict_get(value, "messages"))
{
Some(VmValue::List(messages)) => messages.as_ref().clone(),
_ => Vec::new(),
}
}
pub(super) fn register_visible_message_primitives(vm: &mut Vm) {
harn_vm_core::builtin_support::register_module_builtins(vm, module_path!());
}