use serde::{Deserialize, Serialize};
pub const MIN_FREE_BYTES: u64 = 1024 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct WriteLimits {
pub per_call: Option<u64>,
pub per_run: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WriteVerdict {
Allow,
OutOfSpace {
available: u64,
required: u64,
},
CallTooLarge {
bytes: u64,
limit: u64,
},
RunTooLarge {
written: u64,
limit: u64,
},
}
impl WriteVerdict {
pub fn refusal(&self) -> Option<String> {
match self {
Self::Allow => None,
Self::OutOfSpace {
available,
required,
} => Some(format!(
"[denied] Refusing to write: only {available} bytes are free on this filesystem \
and {required} must remain. This is not a limit you can raise - the machine is \
nearly out of disk. Free some space, or write somewhere with room."
)),
Self::CallTooLarge { bytes, limit } => Some(format!(
"[denied] This call would write {bytes} bytes, over the {limit}-byte per-call \
limit. Write less in one go, or raise `[limits] max_tool_call_write_bytes` in \
the Leviath config (deleting the line removes the limit)."
)),
Self::RunTooLarge { written, limit } => Some(format!(
"[denied] This run has written {written} bytes, over its {limit}-byte budget. \
Raise `[limits] max_run_write_bytes` in the Leviath config, or delete the line \
to remove the limit."
)),
}
}
}
pub fn check_write(
limits: WriteLimits,
already_written: u64,
bytes: u64,
available: Option<u64>,
) -> WriteVerdict {
if let Some(available) = available
&& available.saturating_sub(bytes) < MIN_FREE_BYTES
{
return WriteVerdict::OutOfSpace {
available,
required: MIN_FREE_BYTES,
};
}
if let Some(limit) = limits.per_call
&& bytes > limit
{
return WriteVerdict::CallTooLarge { bytes, limit };
}
let total = already_written.saturating_add(bytes);
if let Some(limit) = limits.per_run
&& total > limit
{
return WriteVerdict::RunTooLarge {
written: total,
limit,
};
}
WriteVerdict::Allow
}
#[cfg(test)]
mod tests;