Skip to main content

soma_codemode/
runner_io.rs

1use tokio::io::AsyncWriteExt;
2
3use crate::protocol::{CodeModeRunnerInput, CodeModeRunnerOutput};
4use crate::runner::limits::MAX_STDIO_LINE_BYTES;
5use crate::ToolError;
6
7pub fn encode_runner_input(input: &CodeModeRunnerInput) -> Result<String, ToolError> {
8    encode_json_line(input, "input")
9}
10
11pub fn encode_runner_output(output: &CodeModeRunnerOutput) -> String {
12    serde_json::to_string(output).unwrap_or_else(|_| {
13        r#"{"type":"error","kind":"internal_error","message":"failed to encode output"}"#
14            .to_string()
15    })
16}
17
18pub fn decode_runner_input(line: &str) -> Result<CodeModeRunnerInput, ToolError> {
19    if line.len() > MAX_STDIO_LINE_BYTES {
20        return Err(ToolError::Sdk {
21            sdk_kind: "invalid_param".to_string(),
22            message: "runner input line exceeded limit".to_string(),
23        });
24    }
25    serde_json::from_str(line).map_err(|err| ToolError::Sdk {
26        sdk_kind: "invalid_param".to_string(),
27        message: format!("invalid runner input JSON: {err}"),
28    })
29}
30
31pub fn decode_runner_output(line: &str) -> Result<CodeModeRunnerOutput, ToolError> {
32    if line.len() > MAX_STDIO_LINE_BYTES {
33        return Err(ToolError::Sdk {
34            sdk_kind: "invalid_param".to_string(),
35            message: "runner output line exceeded limit".to_string(),
36        });
37    }
38    serde_json::from_str(line).map_err(|err| ToolError::Sdk {
39        sdk_kind: "invalid_param".to_string(),
40        message: format!("invalid runner output JSON: {err}"),
41    })
42}
43
44pub async fn write_runner_input<W: AsyncWriteExt + Unpin>(
45    writer: &mut W,
46    input: &CodeModeRunnerInput,
47) -> Result<(), ToolError> {
48    let mut line = encode_runner_input(input)?;
49    line.push('\n');
50    writer.write_all(line.as_bytes()).await.map_err(|err| {
51        ToolError::internal_message(format!("failed to write runner input: {err}"))
52    })?;
53    writer
54        .flush()
55        .await
56        .map_err(|err| ToolError::internal_message(format!("failed to flush runner input: {err}")))
57}
58
59pub async fn terminate_code_mode_runner(
60    child: &mut tokio::process::Child,
61    _child_pid: Option<u32>,
62) {
63    #[cfg(unix)]
64    if let Some(pid) = _child_pid {
65        use nix::sys::signal::Signal;
66        use nix::unistd::Pid;
67        let _ = nix::sys::signal::killpg(Pid::from_raw(pid as i32), Signal::SIGKILL);
68    }
69    let _ = child.kill().await;
70    let _ = child.wait().await;
71}
72
73fn encode_json_line<T: serde::Serialize>(value: &T, label: &str) -> Result<String, ToolError> {
74    let encoded = serde_json::to_string(value).map_err(|err| ToolError::Sdk {
75        sdk_kind: "internal_error".to_string(),
76        message: format!("failed to encode runner {label}: {err}"),
77    })?;
78    if encoded.len() > MAX_STDIO_LINE_BYTES {
79        return Err(ToolError::Sdk {
80            sdk_kind: "invalid_param".to_string(),
81            message: format!("runner {label} line exceeded limit"),
82        });
83    }
84    Ok(encoded)
85}