use monoloop_contracts::{
ChannelId, ExchangeId, SessionId, SessionKey, ToolActionId, TransactionId,
};
pub(crate) fn tool_action_id_for_exchange(
exchange_id: ExchangeId,
provider_tool_call_id: &str,
) -> ToolActionId {
ToolActionId::new(format!("{exchange_id}:{provider_tool_call_id}"))
}
pub(crate) fn provider_tool_call_id_from_action(
exchange_id: ExchangeId,
tool_action_id: &ToolActionId,
) -> Option<&str> {
let prefix = format!("{exchange_id}:");
tool_action_id
.as_str()
.strip_prefix(&prefix)
.filter(|s| !s.is_empty())
}
pub(crate) fn transaction_scoped_session_id(transaction_id: TransactionId) -> SessionId {
SessionId::try_new(format!("tx-{transaction_id}"))
.or_else(|_| SessionId::try_new("direct"))
.expect("session id")
}
pub(crate) fn is_transaction_scoped_session(id: &SessionId, transaction_id: TransactionId) -> bool {
let s = id.as_str();
if s == "direct" {
return true;
}
s == transaction_scoped_session_id(transaction_id).as_str()
}
pub(crate) fn session_key_for(
channel_id: ChannelId,
session_id: Option<SessionId>,
transaction_id: TransactionId,
) -> SessionKey {
let sid = session_id.unwrap_or_else(|| transaction_scoped_session_id(transaction_id));
SessionKey::new(channel_id, sid)
}
pub(crate) fn ensure_session(
session: &mut Option<SessionId>,
preferred: Option<SessionId>,
transaction_id: TransactionId,
) -> SessionId {
if let Some(s) = preferred {
let upgrade = match session.as_ref() {
None => true,
Some(cur) => is_transaction_scoped_session(cur, transaction_id),
};
if upgrade {
*session = Some(s.clone());
return s;
}
return session.clone().expect("authoritative session present");
}
if let Some(s) = session.clone() {
return s;
}
let s = transaction_scoped_session_id(transaction_id);
*session = Some(s.clone());
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn synthetic_and_authoritative_upgrade() {
let tx = TransactionId::generate();
let mut session = None;
let syn = ensure_session(&mut session, None, tx);
assert!(is_transaction_scoped_session(&syn, tx));
let auth = SessionId::try_new("grok-real").unwrap();
let got = ensure_session(&mut session, Some(auth.clone()), tx);
assert_eq!(got, auth);
let other = SessionId::try_new("grok-other").unwrap();
let kept = ensure_session(&mut session, Some(other), tx);
assert_eq!(kept, auth);
}
#[test]
fn tx_prefix_external_id_is_not_synthetic() {
let tx = TransactionId::generate();
let external = SessionId::try_new("tx-external-not-ours").unwrap();
assert!(!is_transaction_scoped_session(&external, tx));
let mut session = Some(external.clone());
let other = SessionId::try_new("grok-claim").unwrap();
let kept = ensure_session(&mut session, Some(other), tx);
assert_eq!(kept, external);
}
#[test]
fn session_key_matches_scoped_id() {
let tx = TransactionId::generate();
let ch = ChannelId::try_new("llm").unwrap();
let key = session_key_for(ch.clone(), None, tx);
assert_eq!(key.session_id, transaction_scoped_session_id(tx));
assert_eq!(key.channel_id, ch);
}
#[test]
fn exchange_scoped_action_round_trips_provider_id() {
let exchange = ExchangeId::generate();
let action = tool_action_id_for_exchange(exchange, "call_reuse");
assert_eq!(
provider_tool_call_id_from_action(exchange, &action),
Some("call_reuse")
);
let other = ExchangeId::generate();
assert!(provider_tool_call_id_from_action(other, &action).is_none());
}
}