use std::time::Duration;
use scv_core::ToolError;
use serde::Deserialize;
use serde_json::{Value, json};
pub(crate) fn parse_args<T: for<'de> Deserialize<'de>>(value: &Value) -> Result<T, ToolError> {
serde_json::from_value(value.clone())
.map_err(|error| ToolError::invalid_arguments(format!("invalid arguments: {error}")))
}
pub(crate) fn validate_process_args(value: &str) -> Result<(), ToolError> {
if value.trim().is_empty() {
return Err(ToolError::invalid_arguments(
"command or prompt must be non-empty",
));
}
Ok(())
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Timeouts {
pub(crate) default: Duration,
pub(crate) max: Duration,
}
impl Timeouts {
pub(crate) fn resolve(self, requested: Option<u64>) -> Result<Duration, ToolError> {
match requested {
None => Ok(self.default.min(self.max)),
Some(0) => Err(ToolError::invalid_arguments(
"timeout_seconds must be positive",
)),
Some(seconds) if seconds > self.max.as_secs() => {
Err(ToolError::invalid_arguments(format!(
"timeout_seconds {seconds} exceeds the configured maximum of {} seconds \
(tools.max_timeout_seconds)",
self.max.as_secs()
)))
}
Some(seconds) => Ok(Duration::from_secs(seconds)),
}
}
}
pub(crate) fn timeout_schema(timeouts: Timeouts) -> Value {
json!({
"type":"integer",
"minimum":1,
"maximum":timeouts.max.as_secs(),
"description":format!(
"Seconds before the process is killed. Defaults to {}; at most {}. \
Raise it for long work such as builds, releases, or landing a change.",
timeouts.default.min(timeouts.max).as_secs(),
timeouts.max.as_secs()
)
})
}
pub(crate) fn bounded(value: &str, max_chars: usize) -> String {
let mut output: String = value.chars().take(max_chars).collect();
if value.chars().count() > max_chars {
output.push('…');
}
output
}