Skip to main content

lash_core/
lashlang_bridge.rs

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