use std::time::Duration;
use rmcp::model::{CallToolResult, ContentBlock, ErrorData, JsonObject};
use serde_json::Value;
use crate::result::{RunResult, RunStatus};
pub(super) fn unknown_run(run_id: &str, retained: Duration) -> String {
format!(
"no run {run_id}. A run is collectable while it is going and for {} after it finishes; anything older has been evicted.",
humantime::format_duration(retained)
)
}
pub(super) fn run_result(run: &RunResult) -> Result<CallToolResult, ErrorData> {
let text = run.text();
let failed = matches!(run.status(), RunStatus::Failed);
let structured = serde_json::to_value(run.to_wire())
.map_err(|e| ErrorData::internal_error(format!("render the run result: {e}"), None))?;
let content = vec![ContentBlock::text(text)];
let mut result = if failed {
CallToolResult::error(content)
} else {
CallToolResult::success(content)
};
result.structured_content = Some(structured);
Ok(result)
}
pub(super) fn text_error(message: String) -> CallToolResult {
CallToolResult::error(vec![ContentBlock::text(message)])
}
pub(super) fn required_string(
arguments: Option<&JsonObject>,
key: &str,
) -> Result<String, ErrorData> {
match arguments.and_then(|arguments| arguments.get(key)) {
Some(Value::String(value)) => Ok(value.clone()),
Some(_) => Err(ErrorData::invalid_params(
format!("{key} must be a string"),
None,
)),
None => Err(ErrorData::invalid_params(
format!("{key} is required"),
None,
)),
}
}
pub(super) fn optional_string(
arguments: Option<&JsonObject>,
key: &str,
) -> Result<String, ErrorData> {
match arguments.and_then(|arguments| arguments.get(key)) {
None => Ok(String::new()),
Some(Value::String(value)) => Ok(value.clone()),
Some(_) => Err(ErrorData::invalid_params(
format!("{key} must be a string"),
None,
)),
}
}
#[cfg(test)]
mod tests {
use rmcp::model::{ErrorCode, JsonObject};
use serde_json::{Value, json};
use super::optional_string;
fn object(value: Value) -> JsonObject {
match value {
Value::Object(map) => map,
other => panic!("arguments must be an object, got {other}"),
}
}
#[test]
fn an_absent_optional_string_defaults_but_an_explicit_null_is_a_client_bug() {
let present = object(json!({ "args": "x" }));
assert_eq!(
optional_string(Some(&present), "args").expect("a string is taken as itself"),
"x"
);
let absent = object(json!({}));
assert_eq!(
optional_string(Some(&absent), "args").expect("an absent field is the default"),
""
);
assert_eq!(
optional_string(None, "args").expect("no arguments is the default"),
""
);
let null = object(json!({ "args": Value::Null }));
let error = optional_string(Some(&null), "args")
.expect_err("an explicit null is not the string the schema declared");
assert_eq!(error.code, ErrorCode::INVALID_PARAMS);
}
}