use crate::harness::types::{ExecInvocation, HarnessEntry};
use crate::parser::ast::ExecBlock;
use std::collections::HashMap;
pub fn build_invocation(
entry: &HarnessEntry,
exec_block: &ExecBlock,
resolved_prompt_path: &str,
cwd: &str,
) -> ExecInvocation {
let mut command = vec![entry.command.clone()];
if let Some(ref slot) = entry.prompt_slot {
if !slot.is_empty() {
command.push(slot.clone());
}
}
command.push(resolved_prompt_path.to_string());
let mut all_args: HashMap<String, String> = HashMap::new();
if let Some(defaults) = &entry.defaults {
all_args.extend(defaults.clone());
}
if let Some(args) = &exec_block.args {
all_args.extend(args.clone());
}
let mut sorted_args: Vec<_> = all_args.into_iter().collect();
sorted_args.sort_by(|a, b| a.0.cmp(&b.0));
for (key, value) in sorted_args {
command.push(format!("--{}", key));
command.push(value);
}
ExecInvocation {
command,
cwd: cwd.to_string(),
env: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_basic_invocation() {
let entry = HarnessEntry {
command: "claude".to_string(),
prompt_slot: Some("-p".to_string()),
args_mapping: "cli".to_string(),
output_mode: None,
defaults: None,
};
let exec_block = ExecBlock {
harness: "claude".to_string(),
prompt: Some("do stuff".to_string()),
prompt_file: None,
args: None,
line: 1,
column: 1,
};
let inv = build_invocation(&entry, &exec_block, "/tmp/prompt.md", "/work");
assert_eq!(inv.command, vec!["claude", "-p", "/tmp/prompt.md"]);
assert_eq!(inv.cwd, "/work");
}
#[test]
fn test_build_invocation_empty_prompt_slot() {
let entry = HarnessEntry {
command: "my-tool".to_string(),
prompt_slot: Some("".to_string()),
args_mapping: "cli".to_string(),
output_mode: None,
defaults: None,
};
let exec_block = ExecBlock {
harness: "my-tool".to_string(),
prompt: None,
prompt_file: Some("prompt.md".to_string()),
args: None,
line: 1,
column: 1,
};
let inv = build_invocation(&entry, &exec_block, "/tmp/prompt.md", "/work");
assert_eq!(inv.command, vec!["my-tool", "/tmp/prompt.md"]);
}
#[test]
fn test_build_invocation_with_defaults() {
let mut defaults = HashMap::new();
defaults.insert("model".to_string(), "opus".to_string());
defaults.insert("max-tokens".to_string(), "1000".to_string());
let entry = HarnessEntry {
command: "claude".to_string(),
prompt_slot: Some("-p".to_string()),
args_mapping: "cli".to_string(),
output_mode: None,
defaults: Some(defaults),
};
let exec_block = ExecBlock {
harness: "claude".to_string(),
prompt: Some("do stuff".to_string()),
prompt_file: None,
args: None,
line: 1,
column: 1,
};
let inv = build_invocation(&entry, &exec_block, "/tmp/prompt.md", "/work");
assert_eq!(inv.command[0], "claude");
assert_eq!(inv.command[1], "-p");
assert_eq!(inv.command[2], "/tmp/prompt.md");
assert!(inv.command.contains(&"--max-tokens".to_string()));
assert!(inv.command.contains(&"1000".to_string()));
assert!(inv.command.contains(&"--model".to_string()));
assert!(inv.command.contains(&"opus".to_string()));
}
#[test]
fn test_build_invocation_exec_args_override_defaults() {
let mut defaults = HashMap::new();
defaults.insert("model".to_string(), "opus".to_string());
defaults.insert("max-tokens".to_string(), "1000".to_string());
let mut exec_args = HashMap::new();
exec_args.insert("model".to_string(), "sonnet".to_string());
let entry = HarnessEntry {
command: "claude".to_string(),
prompt_slot: Some("-p".to_string()),
args_mapping: "cli".to_string(),
output_mode: None,
defaults: Some(defaults),
};
let exec_block = ExecBlock {
harness: "claude".to_string(),
prompt: Some("do stuff".to_string()),
prompt_file: None,
args: Some(exec_args),
line: 1,
column: 1,
};
let inv = build_invocation(&entry, &exec_block, "/tmp/prompt.md", "/work");
let model_idx = inv.command.iter().position(|s| s == "--model").unwrap();
assert_eq!(inv.command[model_idx + 1], "sonnet");
let tokens_idx = inv
.command
.iter()
.position(|s| s == "--max-tokens")
.unwrap();
assert_eq!(inv.command[tokens_idx + 1], "1000");
}
#[test]
fn test_build_invocation_exec_args_add_new() {
let entry = HarnessEntry {
command: "claude".to_string(),
prompt_slot: Some("-p".to_string()),
args_mapping: "cli".to_string(),
output_mode: None,
defaults: None,
};
let mut exec_args = HashMap::new();
exec_args.insert("verbose".to_string(), "true".to_string());
let exec_block = ExecBlock {
harness: "claude".to_string(),
prompt: Some("do stuff".to_string()),
prompt_file: None,
args: Some(exec_args),
line: 1,
column: 1,
};
let inv = build_invocation(&entry, &exec_block, "/tmp/prompt.md", "/work");
assert_eq!(
inv.command,
vec!["claude", "-p", "/tmp/prompt.md", "--verbose", "true"]
);
}
}