use std::path::Path;
use super::content_reference::{
DiffContentReference, PlanContentReference, PromptContentReference, MAX_INLINE_CONTENT_SIZE,
};
use crate::workspace::Workspace;
pub struct PromptContentBuilder<'a> {
workspace: &'a dyn Workspace,
prompt_ref: Option<PromptContentReference>,
plan_ref: Option<PlanContentReference>,
diff_ref: Option<DiffContentReference>,
}
impl<'a> PromptContentBuilder<'a> {
pub fn new(workspace: &'a dyn Workspace) -> Self {
Self {
workspace,
prompt_ref: None,
plan_ref: None,
diff_ref: None,
}
}
#[must_use]
pub fn with_prompt(self, content: String) -> Self {
let backup_path = self.workspace.prompt_backup();
Self {
prompt_ref: Some(PromptContentReference::from_content(
content,
&backup_path,
"Original user requirements from PROMPT.md",
)),
..self
}
}
#[must_use]
pub fn with_plan(self, content: String) -> Self {
let plan_path = Path::new(".agent/PLAN.md");
let xml_fallback = Path::new(".agent/tmp/plan.xml");
Self {
plan_ref: Some(PlanContentReference::from_plan(
content,
plan_path,
Some(xml_fallback),
)),
..self
}
}
#[must_use]
pub fn with_diff(self, content: String, start_commit: &str) -> Self {
let is_oversize = content.len() > MAX_INLINE_CONTENT_SIZE;
if is_oversize {
let tmp_dir = Path::new(".agent/tmp");
let diff_rel = tmp_dir.join("diff.txt");
if self.workspace.create_dir_all(tmp_dir).is_ok() {
let _ = self.workspace.write(&diff_rel, &content);
}
}
let diff_abs = self.workspace.absolute(Path::new(".agent/tmp/diff.txt"));
Self {
diff_ref: Some(DiffContentReference::from_diff(
content,
start_commit,
&diff_abs,
)),
..self
}
}
#[must_use]
pub fn build(self) -> PromptContentReferences {
PromptContentReferences {
prompt: self.prompt_ref,
plan: self.plan_ref,
diff: self.diff_ref,
}
}
#[must_use]
pub fn has_oversize_content(&self) -> bool {
let prompt_oversize = self.prompt_ref.as_ref().is_some_and(|r| !r.is_inline());
let plan_oversize = self.plan_ref.as_ref().is_some_and(|r| !r.is_inline());
let diff_oversize = self.diff_ref.as_ref().is_some_and(|r| !r.is_inline());
prompt_oversize || plan_oversize || diff_oversize
}
}
pub struct PromptContentReferences {
pub prompt: Option<PromptContentReference>,
pub plan: Option<PlanContentReference>,
pub diff: Option<DiffContentReference>,
}
impl PromptContentReferences {
#[must_use]
pub fn prompt_for_template(&self) -> String {
self.prompt
.as_ref()
.map(super::content_reference::PromptContentReference::render_for_template)
.unwrap_or_default()
}
#[must_use]
pub fn plan_for_template(&self) -> String {
self.plan
.as_ref()
.map(super::content_reference::PlanContentReference::render_for_template)
.unwrap_or_default()
}
#[must_use]
pub fn diff_for_template(&self) -> String {
self.diff
.as_ref()
.map(super::content_reference::DiffContentReference::render_for_template)
.unwrap_or_default()
}
#[must_use]
pub fn prompt_is_inline(&self) -> bool {
self.prompt
.as_ref()
.is_some_and(super::content_reference::PromptContentReference::is_inline)
}
#[must_use]
pub fn plan_is_inline(&self) -> bool {
self.plan
.as_ref()
.is_some_and(super::content_reference::PlanContentReference::is_inline)
}
#[must_use]
pub fn diff_is_inline(&self) -> bool {
self.diff
.as_ref()
.is_some_and(super::content_reference::DiffContentReference::is_inline)
}
}
#[cfg(all(test, feature = "test-utils"))]
mod tests {
use super::*;
use crate::prompts::MAX_INLINE_CONTENT_SIZE;
use crate::workspace::MemoryWorkspace;
#[test]
fn test_builder_small_content() {
let workspace = MemoryWorkspace::new_test().with_file("PROMPT.md", "test");
let builder = PromptContentBuilder::new(&workspace)
.with_prompt("Small prompt".to_string())
.with_plan("Small plan".to_string());
assert!(!builder.has_oversize_content());
let refs = builder.build();
assert_eq!(refs.prompt_for_template(), "Small prompt");
assert_eq!(refs.plan_for_template(), "Small plan");
}
#[test]
fn test_builder_large_prompt() {
let workspace = MemoryWorkspace::new_test().with_file(".agent/PROMPT.md.backup", "backup");
let large_content = "x".repeat(MAX_INLINE_CONTENT_SIZE + 1);
let builder = PromptContentBuilder::new(&workspace).with_prompt(large_content);
assert!(builder.has_oversize_content());
let refs = builder.build();
let rendered = refs.prompt_for_template();
assert!(rendered.contains("PROMPT.md.backup"));
assert!(!refs.prompt_is_inline());
}
#[test]
fn test_builder_large_plan() {
let workspace = MemoryWorkspace::new_test().with_file(".agent/PLAN.md", "plan");
let large_content = "x".repeat(MAX_INLINE_CONTENT_SIZE + 1);
let builder = PromptContentBuilder::new(&workspace).with_plan(large_content);
assert!(builder.has_oversize_content());
let refs = builder.build();
let rendered = refs.plan_for_template();
assert!(rendered.contains(".agent/PLAN.md"));
assert!(rendered.contains("plan.xml"));
assert!(!refs.plan_is_inline());
}
#[test]
fn test_builder_large_diff() {
let workspace = MemoryWorkspace::new_test();
let large_content = "x".repeat(MAX_INLINE_CONTENT_SIZE + 1);
let builder = PromptContentBuilder::new(&workspace).with_diff(large_content, "abc123def");
assert!(builder.has_oversize_content());
let refs = builder.build();
let rendered = refs.diff_for_template();
assert!(
rendered.contains(".agent/tmp/diff.txt"),
"Oversize diff should reference .agent/tmp/diff.txt: {}",
&rendered[..rendered.len().min(200)]
);
assert!(workspace.was_written(".agent/tmp/diff.txt"));
assert!(!refs.diff_is_inline());
}
#[test]
fn test_builder_no_oversize_when_all_small() {
let workspace = MemoryWorkspace::new_test().with_file("PROMPT.md", "test");
let builder = PromptContentBuilder::new(&workspace)
.with_prompt("Small prompt".to_string())
.with_plan("Small plan".to_string())
.with_diff("Small diff".to_string(), "abc123");
assert!(!builder.has_oversize_content());
let refs = builder.build();
assert!(refs.prompt_is_inline());
assert!(refs.plan_is_inline());
assert!(refs.diff_is_inline());
}
#[test]
fn test_builder_partial_oversize() {
let workspace = MemoryWorkspace::new_test().with_file(".agent/PROMPT.md.backup", "backup");
let large_prompt = "x".repeat(MAX_INLINE_CONTENT_SIZE + 1);
let builder = PromptContentBuilder::new(&workspace)
.with_prompt(large_prompt)
.with_plan("Small plan".to_string())
.with_diff("Small diff".to_string(), "abc123");
assert!(builder.has_oversize_content());
let refs = builder.build();
assert!(!refs.prompt_is_inline());
assert!(refs.plan_is_inline());
assert!(refs.diff_is_inline());
}
#[test]
fn test_builder_empty_content() {
let workspace = MemoryWorkspace::new_test();
let refs = PromptContentBuilder::new(&workspace).build();
assert_eq!(refs.prompt_for_template(), "");
assert_eq!(refs.plan_for_template(), "");
assert_eq!(refs.diff_for_template(), "");
}
#[test]
fn test_refs_inline_checks_with_none() {
let refs = PromptContentReferences {
prompt: None,
plan: None,
diff: None,
};
assert!(!refs.prompt_is_inline());
assert!(!refs.plan_is_inline());
assert!(!refs.diff_is_inline());
}
}