use std::collections::HashMap;
use std::path::Path;
use serde_json::Value;
pub const DEFAULT_BEHAVIOR_PROMPT: &str = "\
# Task execution
- Do not propose changes to files you have not read. Read first, then modify.
- Do not add features or improvements beyond what was asked.
- Do not create files unless absolutely necessary. Prefer editing existing files.
- If an approach fails, diagnose why before switching tactics.
# Tool usage
- Do NOT use bash when a dedicated tool exists (read_file over cat, edit_file over sed, grep over rg, glob over find).
- Call multiple tools in a single response. Make independent calls in parallel.
- If tool calls depend on previous results, call them sequentially — do not guess parameters.
# Safety concerns
- Consider the reversibility and impact of actions before executing them.
- Prefer reversible operations over destructive ones when both achieve the goal.
- If an approach fails, diagnose the root cause before retrying or switching tactics.
# Communication
- Be direct. Lead with the answer or action, not the reasoning.
- Keep output concise — omit filler, preamble, and unnecessary transitions.
- Try the simplest approach first.";
pub(crate) const STRUCTURED_OUTPUT_INSTRUCTION: &str =
"\n\nIMPORTANT: You must provide your final response using the StructuredOutput tool \
with the required structured format. After using any other tools needed to complete \
the task, always call StructuredOutput with your final answer in the specified schema.";
pub(crate) const STRUCTURED_OUTPUT_RETRY: &str =
"You MUST call the StructuredOutput tool to complete \
this request. Call this tool now with the required schema.";
pub(crate) const STRUCTURED_OUTPUT_TOOL_DESCRIPTION: &str =
"Return your final response using the required output schema. \
Call this tool exactly once at the end to provide the structured result.";
pub(crate) const STRUCTURED_OUTPUT_TOOL_NAME: &str = "StructuredOutput";
pub(crate) fn interpolate(template: &str, state: &HashMap<String, Value>) -> String {
let mut result = template.to_string();
for (key, value) in state {
let replacement = match value {
Value::String(s) => s.clone(),
other => other.to_string(),
};
result = result.replace(&format!("{{{key}}}"), &replacement);
}
result
}
pub(crate) fn collect_metadata(cwd: &Path) -> String {
let working_directory = cwd.display();
let platform = std::env::consts::OS;
let os_version = std::process::Command::new("uname")
.arg("-r")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_default();
let date = format_current_date();
format!(
"<environment>\nWorking directory: {working_directory}\nPlatform: {platform}\nOS version: {os_version}\nDate: {date}\n</environment>"
)
}
fn format_current_date() -> String {
let epoch_secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let days = epoch_secs / 86400;
let z = days + 719468;
let era = z / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let year = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if month <= 2 { year + 1 } else { year };
format!("{year:04}-{month:02}-{day:02}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metadata_includes_path() {
let ctx = collect_metadata(std::path::Path::new("/home/user/project"));
assert!(ctx.contains("/home/user/project"));
assert!(ctx.contains("<environment>"));
}
#[test]
fn interpolate_substitutes_placeholders() {
let mut vars: HashMap<String, Value> = HashMap::new();
vars.insert("name".into(), Value::String("Alice".into()));
vars.insert("count".into(), Value::from(3));
let out = interpolate("Hello {name}, you have {count} tasks.", &vars);
assert_eq!(out, "Hello Alice, you have 3 tasks.");
}
}