use super::{Error, reference, usage};
use crate::template::GitRunner;
use std::path::{Path, PathBuf};
pub(in crate::prompt) fn point(
worktree: &Path,
agent_id: &str,
budget: u32,
git: &dyn GitRunner,
) -> Result<Option<String>, Error> {
let Some(tip) = usage::last(worktree)? else {
return Ok(None);
};
let budget = u64::from(budget);
let mut point = None;
for (sha, path) in entries(worktree, agent_id, git)? {
let Some(report) = read(worktree, &sha, &path, git)? else {
continue;
};
if tip.prompt_tokens.saturating_sub(report.prompt_tokens) > budget {
return Ok(point);
}
point = Some(sha);
}
Ok(None)
}
fn entries(
worktree: &Path,
agent_id: &str,
git: &dyn GitRunner,
) -> Result<Vec<(String, PathBuf)>, Error> {
let range = match reference::origin(worktree, "HEAD", agent_id, git)? {
Some(sha) => format!("{sha}..HEAD"),
None => "HEAD".to_string(),
};
let dir = format!("{}/", usage::MESSAGES_DIR);
let out = git
.run_capture(
worktree,
&[
"log",
"--format=%H",
"--name-only",
"--diff-filter=A",
&range,
"--",
&dir,
],
)
.map_err(|source| Error::Git {
op: "token tail log",
source,
})?;
let mut found = Vec::new();
let mut sha = String::new();
for line in out.lines().filter(|l| !l.trim().is_empty()) {
if let Some(rest) = line.strip_prefix(&dir) {
let path = PathBuf::from(line);
if usage::model_entry(Path::new(rest)).is_some() {
found.push((sha.clone(), path));
}
} else {
sha = line.trim().to_string();
}
}
Ok(found)
}
fn read(
worktree: &Path,
sha: &str,
path: &Path,
git: &dyn GitRunner,
) -> Result<Option<usage::LastUsage>, Error> {
let name = path.to_string_lossy();
let spec = format!("{sha}:{name}");
let blob = git
.run_capture(worktree, &["show", &spec])
.map_err(|source| Error::Git {
op: "token tail entry read",
source,
})?;
Ok(usage::report(blob.as_bytes(), &name))
}
#[cfg(test)]
mod tests;