use crate::subagent_delegation::SubagentSessionDelegate;
use crate::tools::ToolExecutionResult;
use crate::{session_files::SessionFileSystem, tool_context::ToolContext};
use serde_json::Value;
use std::str::FromStr;
pub fn get_str<'a>(args: &'a Value, key: &str) -> Option<&'a str> {
args.get(key)
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
}
pub fn require_str<'a>(args: &'a Value, key: &str) -> Result<&'a str, ToolExecutionResult> {
get_str(args, key).ok_or_else(|| {
ToolExecutionResult::tool_error(format!("Missing required parameter: {key}"))
})
}
pub fn require_str_trimmed<'a>(args: &'a Value, key: &str) -> Result<&'a str, ToolExecutionResult> {
args.get(key)
.and_then(Value::as_str)
.map(str::trim)
.filter(|s| !s.is_empty())
.ok_or_else(|| {
ToolExecutionResult::tool_error(format!("Missing required parameter: {key}"))
})
}
pub fn require_str_nonblank<'a>(
args: &'a Value,
key: &str,
) -> Result<&'a str, ToolExecutionResult> {
args.get(key)
.and_then(Value::as_str)
.filter(|s| !s.trim().is_empty())
.ok_or_else(|| {
ToolExecutionResult::tool_error(format!("Missing required parameter: {key}"))
})
}
pub fn parse_id<T: FromStr>(raw: &str, label: &str) -> Result<T, ToolExecutionResult> {
raw.parse::<T>()
.map_err(|_| ToolExecutionResult::tool_error(format!("Invalid {label}: {raw}")))
}
pub fn require_id<T: FromStr>(args: &Value, key: &str) -> Result<T, ToolExecutionResult> {
let raw = require_str(args, key)?;
parse_id(raw, key)
}
pub fn get_subagent_delegate(
context: &ToolContext,
) -> Result<&dyn SubagentSessionDelegate, ToolExecutionResult> {
context
.subagent_delegate
.as_ref()
.map(|store| store.as_ref())
.ok_or_else(|| {
ToolExecutionResult::tool_error(
"Platform management not available: platform_store context is missing. Ensure the platform capability is enabled.",
)
})
}
pub fn require_file_store(
context: &ToolContext,
) -> Result<&dyn SessionFileSystem, ToolExecutionResult> {
context
.file_store
.as_ref()
.map(|store| store.as_ref())
.ok_or_else(|| ToolExecutionResult::tool_error("File system not available"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::typed_id::SessionId;
use serde_json::json;
#[test]
fn string_extractors_preserve_their_distinct_whitespace_contracts() {
for (args, raw, trimmed, nonblank) in [
(json!({}), None, None, None),
(json!({"value":null}), None, None, None),
(json!({"value":42}), None, None, None),
(json!({"value":true}), None, None, None),
(json!({"value":[]}), None, None, None),
(json!({"value":{}}), None, None, None),
(json!({"value":""}), None, None, None),
(json!({"value":" \t\n"}), Some(" \t\n"), None, None),
(
json!({"value":"\u{2003}α\u{2003}"}),
Some("\u{2003}α\u{2003}"),
Some("α"),
Some("\u{2003}α\u{2003}"),
),
(json!({"value":"x"}), Some("x"), Some("x"), Some("x")),
] {
assert_eq!(get_str(&args, "value"), raw, "{args}");
for (actual, expected) in [
(require_str(&args, "value"), raw),
(require_str_trimmed(&args, "value"), trimmed),
(require_str_nonblank(&args, "value"), nonblank),
] {
match (actual, expected) {
(Ok(value), Some(expected)) => assert_eq!(value, expected, "{args}"),
(Err(ToolExecutionResult::ToolError(message)), None) => {
assert_eq!(message, "Missing required parameter: value")
}
(actual, expected) => panic!("{args}: expected {expected:?}, got {actual:?}"),
}
}
}
}
#[test]
fn required_id_preserves_identity_and_distinguishes_missing_from_invalid() {
let wire = "session_12345678123456789abc123456789abc";
let expected = uuid::Uuid::parse_str("12345678-1234-5678-9abc-123456789abc").unwrap();
assert_eq!(
require_id::<SessionId>(&json!({"session_id":wire}), "session_id")
.unwrap()
.uuid(),
expected
);
assert_eq!(
parse_id::<SessionId>(wire, "session").unwrap().uuid(),
expected
);
for args in [
json!({}),
json!({"session_id":""}),
json!({"session_id":null}),
json!({"session_id":42}),
] {
assert!(
matches!(require_id::<SessionId>(&args,"session_id"),Err(ToolExecutionResult::ToolError(message)) if message == "Missing required parameter: session_id")
);
}
for raw in [
"not-a-valid-id",
"agent_12345678123456789abc123456789abc",
" ",
"session_1234",
] {
assert!(
matches!(require_id::<SessionId>(&json!({"session_id":raw}),"session_id"),Err(ToolExecutionResult::ToolError(message)) if message == format!("Invalid session_id: {raw}"))
);
}
}
#[test]
fn parse_id_uses_custom_label() {
let err = parse_id::<SessionId>("garbage", "harness id").unwrap_err();
match err {
ToolExecutionResult::ToolError(msg) => {
assert_eq!(msg, "Invalid harness id: garbage");
}
other => panic!("expected ToolError, got {other:?}"),
}
}
}