use agtrace_sdk::{
Client,
types::{SessionFilter, ToolCallPayload},
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum ExecuteIntent {
Read,
Write,
Build,
Test,
Git,
Other,
}
impl ExecuteIntent {
fn classify(command: &str) -> Self {
let cmd = command.trim();
let first_word = cmd.split_whitespace().next().unwrap_or("");
if Self::is_read_command(cmd, first_word) {
return ExecuteIntent::Read;
}
if Self::is_write_command(cmd, first_word) {
return ExecuteIntent::Write;
}
if cmd.contains("build") || cmd.contains("compile") || first_word == "make" {
return ExecuteIntent::Build;
}
if cmd.contains("test") || cmd.contains("pytest") || cmd.contains("jest") {
return ExecuteIntent::Test;
}
if first_word == "git" || first_word == "gh" {
return ExecuteIntent::Git;
}
ExecuteIntent::Other
}
fn is_read_command(cmd: &str, first_word: &str) -> bool {
match first_word {
"cat" | "head" | "tail" | "less" | "more" => true,
"grep" | "rg" | "ag" | "ack" => true,
"ls" | "find" | "tree" | "fd" => true,
"wc" | "diff" | "stat" | "file" => true,
"sed" => {
!Self::has_option(cmd, "-i") && !Self::has_option(cmd, "--in-place")
}
"awk" => {
!cmd.contains(">")
}
"bash" => {
if let Some(inner) = Self::extract_bash_inner_command(cmd) {
let inner_first = inner.split_whitespace().next().unwrap_or("");
Self::is_read_command(&inner, inner_first)
} else {
false
}
}
_ => false,
}
}
fn is_write_command(cmd: &str, first_word: &str) -> bool {
if Self::is_read_command(cmd, first_word) {
return false;
}
match first_word {
"rm" | "mv" | "cp" | "mkdir" | "rmdir" | "touch" => true,
"echo" | "printf" if cmd.contains(">") => true,
"sed" if Self::has_option(cmd, "-i") || Self::has_option(cmd, "--in-place") => true,
"awk" if cmd.contains(">") => true,
"bash" => {
if let Some(inner) = Self::extract_bash_inner_command(cmd) {
let inner_first = inner.split_whitespace().next().unwrap_or("");
Self::is_write_command(&inner, inner_first)
} else {
false
}
}
_ => false,
}
}
fn extract_bash_inner_command(cmd: &str) -> Option<String> {
if let Some(idx) = cmd.find("-lc") {
let rest = cmd[idx + 3..].trim();
return Some(rest.to_string());
}
if let Some(idx) = cmd.find("-c") {
let rest = cmd[idx + 2..].trim();
return Some(rest.to_string());
}
None
}
fn has_option(cmd: &str, option: &str) -> bool {
cmd.split_whitespace().any(|word| {
word == option
|| word.starts_with(option)
&& (word.len() == option.len()
|| !word
.chars()
.nth(option.len())
.unwrap_or(' ')
.is_alphanumeric())
})
}
}
impl std::fmt::Display for ExecuteIntent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExecuteIntent::Read => write!(f, "Read"),
ExecuteIntent::Write => write!(f, "Write"),
ExecuteIntent::Build => write!(f, "Build"),
ExecuteIntent::Test => write!(f, "Test"),
ExecuteIntent::Git => write!(f, "Git"),
ExecuteIntent::Other => write!(f, "Other"),
}
}
}
#[derive(Default)]
struct ProviderStats {
total_execute: usize,
intent_counts: HashMap<ExecuteIntent, usize>,
intent_examples: HashMap<ExecuteIntent, Vec<String>>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== agtrace SDK: Execute Command Breakdown ===\n");
let client = Client::connect_default().await?;
println!("✓ Connected to workspace\n");
let sessions = client.sessions().list(SessionFilter::all())?;
if sessions.is_empty() {
println!("No sessions found. Start an agent session first.");
return Ok(());
}
println!("Analyzing {} sessions...\n", sessions.len());
let mut provider_stats: HashMap<String, ProviderStats> = HashMap::new();
let mut total_execute = 0;
for session_summary in &sessions {
let session_handle = client.sessions().get(&session_summary.id)?;
let provider = &session_summary.provider;
let stats = provider_stats.entry(provider.clone()).or_default();
if let Ok(session) = session_handle.assemble() {
for turn in &session.turns {
for step in &turn.steps {
for tool_exec in &step.tools {
let call = &tool_exec.call.content;
if let ToolCallPayload::Execute { arguments, .. } = call
&& let Some(command) = arguments.command()
{
total_execute += 1;
stats.total_execute += 1;
let intent = ExecuteIntent::classify(command);
*stats.intent_counts.entry(intent).or_insert(0) += 1;
let examples = stats.intent_examples.entry(intent).or_default();
if examples.len() < 3 {
examples.push(command.to_string());
}
}
}
}
}
}
}
println!("Total Execute commands: {}\n", total_execute);
let mut providers: Vec<_> = provider_stats.iter().collect();
providers.sort_by(|a, b| b.1.total_execute.cmp(&a.1.total_execute));
for (provider_name, stats) in providers {
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!(
"Provider: {} (× {} Execute commands)",
provider_name, stats.total_execute
);
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!();
println!(" Execute Intent Breakdown:");
let mut intents: Vec<_> = stats.intent_counts.iter().collect();
intents.sort_by(|a, b| b.1.cmp(a.1));
for (intent, count) in intents {
let percentage = (*count as f64 / stats.total_execute as f64) * 100.0;
println!(" {} (× {}, {:.1}%)", intent, count, percentage);
if let Some(examples) = stats.intent_examples.get(intent) {
for example in examples {
let truncated = if example.len() > 80 {
format!("{}...", &example[..77])
} else {
example.clone()
};
println!(" - {}", truncated);
}
}
}
println!();
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_classify_sed_read() {
assert_eq!(
ExecuteIntent::classify("sed -n 1,200p packages/extension-inspector/src/App.tsx"),
ExecuteIntent::Read
);
assert_eq!(
ExecuteIntent::classify("sed -n '1,160p' docs/provider_tool_normalization.md"),
ExecuteIntent::Read
);
}
#[test]
fn test_classify_sed_write() {
assert_eq!(
ExecuteIntent::classify("sed -i 's/foo/bar/g' file.txt"),
ExecuteIntent::Write
);
assert_eq!(
ExecuteIntent::classify("sed --in-place 's/foo/bar/g' file.txt"),
ExecuteIntent::Write
);
}
#[test]
fn test_classify_bash_wrapped_read() {
assert_eq!(
ExecuteIntent::classify("bash -lc cat Cargo.toml"),
ExecuteIntent::Read
);
assert_eq!(
ExecuteIntent::classify("bash -lc sed -n '1,200p' src/main.rs"),
ExecuteIntent::Read
);
}
#[test]
fn test_classify_ls() {
assert_eq!(ExecuteIntent::classify("ls"), ExecuteIntent::Read);
assert_eq!(ExecuteIntent::classify("ls -la"), ExecuteIntent::Read);
}
#[test]
fn test_classify_cat() {
assert_eq!(ExecuteIntent::classify("cat file.txt"), ExecuteIntent::Read);
}
#[test]
fn test_classify_grep() {
assert_eq!(
ExecuteIntent::classify("grep pattern file.txt"),
ExecuteIntent::Read
);
}
#[test]
fn test_classify_git() {
assert_eq!(ExecuteIntent::classify("git status"), ExecuteIntent::Git);
assert_eq!(
ExecuteIntent::classify("git commit -m 'message'"),
ExecuteIntent::Git
);
}
#[test]
fn test_classify_test() {
assert_eq!(ExecuteIntent::classify("cargo test"), ExecuteIntent::Test);
assert_eq!(ExecuteIntent::classify("npm test"), ExecuteIntent::Test);
}
#[test]
fn test_classify_build() {
assert_eq!(ExecuteIntent::classify("cargo build"), ExecuteIntent::Build);
assert_eq!(ExecuteIntent::classify("npm build"), ExecuteIntent::Build);
}
}