use std::path::{Path, PathBuf};
use super::types::SessionConfig;
pub fn looks_path_like(val: &str) -> bool {
if val.contains('\n') || val.contains(" ") {
return false;
}
let trimmed = val.trim();
if trimmed.is_empty() {
return false;
}
let starts = trimmed.starts_with('/')
|| trimmed.starts_with("./")
|| trimmed.starts_with("../")
|| trimmed.starts_with('~');
let lower = trimmed.to_ascii_lowercase();
let ends = lower.ends_with(".md") || lower.ends_with(".txt");
starts || ends
}
fn resolve_against(val: &str, client_cwd: &Path) -> PathBuf {
let trimmed = val.trim();
if let Some(rest) = trimmed.strip_prefix('~') {
if let Some(home) = dirs::home_dir() {
return home.join(rest.strip_prefix('/').unwrap_or(rest));
}
}
let p = Path::new(trimmed);
if p.is_absolute() {
p.to_path_buf()
} else {
client_cwd.join(p)
}
}
pub fn resolve_client_session_args(
cfg: &mut SessionConfig,
client_cwd: &Path,
) -> Result<(), String> {
if let Some(ref val) = cfg.system {
if looks_path_like(val) {
let abs = resolve_against(val, client_cwd);
match std::fs::read_to_string(&abs) {
Ok(contents) => {
cfg.system = Some(contents);
}
Err(_) => {
return Err(format!(
"--system {val}: no such file (resolved {})",
abs.display()
));
}
}
}
}
if let Some(ref val) = cfg.prompt_manifest {
let abs = if val.is_absolute() {
val.clone()
} else {
client_cwd.join(val)
};
if !abs.exists() {
return Err(format!(
"--prompt-manifest {}: no such file (resolved {})",
val.display(),
abs.display()
));
}
cfg.prompt_manifest = Some(abs);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn path_like_detection() {
assert!(looks_path_like("./prompt.md"));
assert!(looks_path_like("../prompt.md"));
assert!(looks_path_like("/tmp/prompt.md"));
assert!(looks_path_like("~/prompts/system.md"));
assert!(looks_path_like("notes.txt"));
assert!(looks_path_like("NOTES.MD"));
assert!(looks_path_like("./foo"));
assert!(!looks_path_like("You are a helpful assistant"));
assert!(!looks_path_like("Be concise and direct. Use tools."));
assert!(!looks_path_like("multi\nline\nprompt"));
assert!(!looks_path_like("has double spaces"));
assert!(!looks_path_like(""));
}
#[test]
fn relative_resolution_against_cwd() {
let cwd = Path::new("/tmp/proj");
let abs = resolve_against("./prompt.md", cwd);
assert_eq!(abs, PathBuf::from("/tmp/proj/prompt.md"));
let abs = resolve_against("../other/p.md", cwd);
assert_eq!(abs, PathBuf::from("/tmp/proj/../other/p.md"));
}
#[test]
fn absolute_path_unchanged() {
let cwd = Path::new("/tmp/proj");
let abs = resolve_against("/etc/prompt.md", cwd);
assert_eq!(abs, PathBuf::from("/etc/prompt.md"));
}
#[test]
fn system_content_replacement() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("p.md");
std::fs::write(&file, "You are PROOF-T4").unwrap();
let mut cfg = SessionConfig {
system: Some("./p.md".into()),
..Default::default()
};
resolve_client_session_args(&mut cfg, dir.path()).unwrap();
assert_eq!(cfg.system.as_deref(), Some("You are PROOF-T4"));
}
#[test]
fn system_missing_file_errors() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = SessionConfig {
system: Some("./missing.md".into()),
..Default::default()
};
let err = resolve_client_session_args(&mut cfg, dir.path()).unwrap_err();
assert!(err.contains("--system ./missing.md"), "err={err}");
assert!(err.contains("no such file"), "err={err}");
assert!(
err.contains("missing.md"),
"err={err}"
);
}
#[test]
fn system_prose_passes_through() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = SessionConfig {
system: Some("You are a helpful assistant".into()),
..Default::default()
};
resolve_client_session_args(&mut cfg, dir.path()).unwrap();
assert_eq!(
cfg.system.as_deref(),
Some("You are a helpful assistant")
);
}
#[test]
fn prompt_manifest_canonicalized() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("manifest.toml");
std::fs::write(&file, "[prompt]").unwrap();
let mut cfg = SessionConfig {
prompt_manifest: Some(PathBuf::from("manifest.toml")),
..Default::default()
};
resolve_client_session_args(&mut cfg, dir.path()).unwrap();
assert!(cfg.prompt_manifest.as_ref().unwrap().is_absolute());
}
#[test]
fn prompt_manifest_missing_errors() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = SessionConfig {
prompt_manifest: Some(PathBuf::from("nope.toml")),
..Default::default()
};
let err = resolve_client_session_args(&mut cfg, dir.path()).unwrap_err();
assert!(err.contains("--prompt-manifest"), "err={err}");
assert!(err.contains("no such file"), "err={err}");
}
}