use crate::engine::SymbolicAnswer;
use crate::event_log::EventLog;
use crate::language::Language;
use crate::seed::{self, TerminalCommandVocabulary};
use crate::solver_handlers::finalize_simple;
fn extract_backtick_command(prompt: &str) -> Option<String> {
let bytes: Vec<char> = prompt.chars().collect();
let first = bytes.iter().position(|&c| c == '`')?;
let mut start = first;
while start < bytes.len() && bytes[start] == '`' {
start += 1;
}
let mut end = start;
while end < bytes.len() && bytes[end] != '`' {
end += 1;
}
if end <= start {
return None;
}
let command: String = bytes[start..end].iter().collect();
let command = command.trim();
if command.is_empty() {
None
} else {
Some(command.to_owned())
}
}
fn word_tokens(lower: &str) -> Vec<String> {
lower
.split(|c: char| !(c.is_alphanumeric() || c == '_'))
.filter(|t| !t.is_empty())
.map(ToOwned::to_owned)
.collect()
}
fn leading_shell_command(prompt: &str, vocab: &TerminalCommandVocabulary) -> Option<String> {
let trimmed = prompt.trim().trim_matches('`').trim();
let first = trimmed.split_whitespace().next()?;
let normalized: String = first
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '-')
.collect::<String>()
.to_lowercase();
if vocab.shell_tokens.iter().any(|t| t == &normalized) {
Some(trimmed.to_owned())
} else {
None
}
}
fn detect_terminal_command(prompt: &str, vocab: &TerminalCommandVocabulary) -> Option<String> {
let lower = prompt.to_lowercase();
let has_phrase = vocab.terminal_phrases.iter().any(|p| lower.contains(p));
let tokens = word_tokens(&lower);
let has_verb = vocab
.run_verbs
.iter()
.any(|v| tokens.iter().any(|t| t == v))
|| vocab.cjk_run_verbs.iter().any(|v| lower.contains(v));
let backtick = extract_backtick_command(prompt);
let leading = leading_shell_command(prompt, vocab);
if backtick.is_some() && (has_verb || has_phrase) {
return backtick;
}
if has_phrase && has_verb {
return backtick.or(leading);
}
if let Some(cmd) = leading {
return Some(cmd);
}
None
}
#[allow(clippy::literal_string_with_formatting_args)]
fn terminal_body(command: &str, language: Language) -> String {
let template = seed::response_for("agent_suggestion", language.slug())
.or_else(|| seed::response_for("agent_suggestion", "en"))
.unwrap_or_default();
template.replace("{command}", command)
}
pub fn try_terminal_command(
prompt: &str,
language: Language,
log: &mut EventLog,
) -> Option<SymbolicAnswer> {
let vocab = seed::terminal_command_vocabulary();
let command = detect_terminal_command(prompt, &vocab)?;
log.append("terminal:command", command.clone());
log.append("terminal:agent_suggestion", "shell".to_owned());
let body = terminal_body(&command, language);
Some(finalize_simple(
prompt,
log,
"agent_suggestion",
"response:agent_suggestion",
&body,
0.6,
))
}