Skip to main content

lash_lashlang_runtime/
bridge.rs

1use lashlang::{ExecutionHostError, Value as LashlangValue};
2
3pub fn lashlang_value_to_json(
4    value: &LashlangValue,
5) -> Result<serde_json::Value, ExecutionHostError> {
6    serde_json::to_value(value)
7        .map_err(|err| ExecutionHostError::new(format!("failed to serialize value: {err}")))
8}
9
10pub fn protocol_tool_reply_to_lashlang_value(
11    reply: lash_core::ToolInvocationReply,
12) -> Result<LashlangValue, ExecutionHostError> {
13    let output = reply.output;
14    let is_success = output.is_success();
15    let value = output.into_value_for_projection();
16    if is_success {
17        Ok(lashlang::from_json(value))
18    } else {
19        Err(ExecutionHostError::new(json_error_message(value)))
20    }
21}
22
23pub fn protocol_tool_output_to_lashlang_value(
24    output: &lash_core::ToolCallOutput,
25) -> Result<LashlangValue, ExecutionHostError> {
26    let value = output.value_for_projection();
27    if output.is_success() {
28        Ok(lashlang::from_json(value))
29    } else {
30        Err(ExecutionHostError::new(json_error_message(value)))
31    }
32}
33
34pub fn process_event_payload(
35    value: &LashlangValue,
36) -> Result<serde_json::Value, ExecutionHostError> {
37    Ok(serde_json::json!({
38        "value": lashlang_value_to_json(value)?,
39        "text": value.to_string(),
40    }))
41}
42
43pub fn sleep_duration_ms(
44    kind: lashlang::SleepKind,
45    value: &LashlangValue,
46) -> Result<u64, ExecutionHostError> {
47    match kind {
48        lashlang::SleepKind::For => duration_value_ms(value),
49        lashlang::SleepKind::Until => {
50            let target = deadline_value_ms(value)?;
51            let now = chrono::Utc::now().timestamp_millis();
52            Ok(target.saturating_sub(now.max(0) as u64))
53        }
54    }
55}
56
57fn duration_value_ms(value: &LashlangValue) -> Result<u64, ExecutionHostError> {
58    match value {
59        LashlangValue::Number(value) if value.is_finite() && *value >= 0.0 => {
60            Ok(value.round() as u64)
61        }
62        LashlangValue::String(value) => parse_duration_ms(value),
63        other => Err(ExecutionHostError::new(format!(
64            "`sleep for` expects a non-negative millisecond number or duration string, got {other}"
65        ))),
66    }
67}
68
69fn deadline_value_ms(value: &LashlangValue) -> Result<u64, ExecutionHostError> {
70    match value {
71        LashlangValue::Number(value) if value.is_finite() && *value >= 0.0 => {
72            Ok(value.round() as u64)
73        }
74        LashlangValue::String(value) => chrono::DateTime::parse_from_rfc3339(value)
75            .map(|deadline| deadline.timestamp_millis().max(0) as u64)
76            .map_err(|err| {
77                ExecutionHostError::new(format!(
78                    "`sleep until` expects RFC3339 text or Unix epoch milliseconds: {err}"
79                ))
80            }),
81        other => Err(ExecutionHostError::new(format!(
82            "`sleep until` expects RFC3339 text or Unix epoch milliseconds, got {other}"
83        ))),
84    }
85}
86
87fn parse_duration_ms(value: &str) -> Result<u64, ExecutionHostError> {
88    let value = value.trim();
89    let (number, multiplier) = if let Some(number) = value.strip_suffix("ms") {
90        (number, 1.0)
91    } else if let Some(number) = value.strip_suffix('s') {
92        (number, 1_000.0)
93    } else if let Some(number) = value.strip_suffix('m') {
94        (number, 60_000.0)
95    } else if let Some(number) = value.strip_suffix('h') {
96        (number, 3_600_000.0)
97    } else {
98        (value, 1.0)
99    };
100    let parsed = number
101        .trim()
102        .parse::<f64>()
103        .map_err(|err| ExecutionHostError::new(format!("invalid duration `{value}`: {err}")))?;
104    if !parsed.is_finite() || parsed < 0.0 {
105        return Err(ExecutionHostError::new(format!(
106            "invalid duration `{value}`: expected a non-negative finite number"
107        )));
108    }
109    Ok((parsed * multiplier).round() as u64)
110}
111
112pub fn json_error_message(value: serde_json::Value) -> String {
113    match value {
114        serde_json::Value::String(text) => text,
115        other => other.to_string(),
116    }
117}