use std::{io::BufRead, os::unix::fs::PermissionsExt as _, path::Path};
use serde_json::{Value, json};
const INHERITED_SCV_ENV: &[&str] = &[
"SCV_CONFIG",
"SCV_PARENT",
"SCV_DELEGATION_DEPTH",
"SCV_MODEL",
"SCV_PROVIDER",
"SCV_BASE_URL",
"SCV_API_KEY_ENV",
];
pub(crate) trait Isolated {
fn isolated(&mut self, home: &Path) -> &mut Self;
}
impl Isolated for std::process::Command {
fn isolated(&mut self, home: &Path) -> &mut Self {
if let Some(real) = std::env::var_os("HOME") {
assert!(
!home.starts_with(Path::new(&real).join(".scv")),
"test home {} is inside the real ~/.scv",
home.display()
);
}
let user_home = home.join(".no-user-home");
self.env("SCV_HOME", home).env("HOME", &user_home);
for variable in INHERITED_SCV_ENV {
self.env_remove(variable);
}
self
}
}
impl Isolated for tokio::process::Command {
fn isolated(&mut self, home: &Path) -> &mut Self {
self.as_std_mut().isolated(home);
self
}
}
pub(crate) fn write_private(path: &Path, contents: &str) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, contents).unwrap();
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)).unwrap();
}
pub(crate) fn call(call_id: &str, name: &str, arguments: Value) -> String {
let delta = json!({"type":"response.function_call_arguments.delta","output_index":0,"delta":arguments.to_string()});
let done = json!({"type":"response.output_item.done","output_index":0,"item":{"type":"function_call","call_id":call_id,"name":name}});
format!(
"data: {delta}\n\ndata: {done}\n\ndata: {{\"type\":\"response.completed\",\"response\":{{}}}}\n\n"
)
}
pub(crate) fn text(content: &str) -> String {
let delta = json!({"type":"response.output_text.delta","delta":content});
format!("data: {delta}\n\ndata: {{\"type\":\"response.completed\",\"response\":{{}}}}\n\n")
}
pub(crate) fn read_http_request(reader: &mut impl BufRead) -> Vec<u8> {
let mut length = 0;
loop {
let mut line = String::new();
reader.read_line(&mut line).unwrap();
if line == "\r\n" || line.is_empty() {
break;
}
if let Some((key, value)) = line.split_once(':')
&& key.eq_ignore_ascii_case("content-length")
{
length = value.trim().parse().unwrap();
}
}
let mut body = vec![0; length];
reader.read_exact(&mut body).unwrap();
body
}
pub(crate) fn sse_response(body: &str) -> String {
format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
body.len()
)
}
pub(crate) fn tagged(handle: &str) -> Vec<u32> {
let Ok(entries) = std::fs::read_dir("/proc") else {
return Vec::new();
};
entries
.flatten()
.filter_map(|entry| {
let pid: u32 = entry.file_name().to_str()?.parse().ok()?;
let environ = std::fs::read(entry.path().join("environ")).ok()?;
environ
.split(|byte| *byte == 0)
.filter_map(|entry| entry.strip_prefix(b"SCV_PARENT="))
.any(|chain| {
String::from_utf8_lossy(chain)
.split(';')
.any(|entry| entry.rsplit('/').next() == Some(handle))
})
.then_some(pid)
})
.collect()
}
pub(crate) fn alive(pid: u32) -> bool {
let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) else {
return unsafe { libc::kill(pid as i32, 0) } == 0;
};
!stat
.rsplit(')')
.next()
.is_some_and(|rest| rest.trim_start().starts_with('Z'))
}