zag-agent 0.12.8

Core library for zag — a unified interface for AI coding agents
Documentation
use super::{AVAILABLE_MODELS, Copilot, parse_copilot_event_line};
use crate::sandbox::SandboxConfig;
use crate::session_log::LogEventKind;
use std::collections::HashSet;

#[test]
fn test_build_run_args_non_interactive() {
    let mut copilot = Copilot::new();
    copilot.common.model = "claude-sonnet-4.6".to_string();

    let args = copilot.build_run_args(false, Some("hello"));
    assert!(args.contains(&"--allow-all".to_string()));
    assert!(args.contains(&"--model".to_string()));
    assert!(args.contains(&"claude-sonnet-4.6".to_string()));
    assert!(args.contains(&"-p".to_string()));
    assert!(args.contains(&"hello".to_string()));
}

#[test]
fn test_build_run_args_interactive_with_prompt() {
    let copilot = Copilot::new();
    let args = copilot.build_run_args(true, Some("hello"));
    assert!(!args.contains(&"--allow-all".to_string()));
    assert!(args.contains(&"-i".to_string()));
    assert!(args.contains(&"hello".to_string()));
}

#[test]
fn test_build_run_args_interactive_no_prompt() {
    let copilot = Copilot::new();
    let args = copilot.build_run_args(true, None);
    assert!(!args.contains(&"-i".to_string()));
    assert!(!args.contains(&"-p".to_string()));
}

#[test]
fn test_build_run_args_skip_permissions() {
    let mut copilot = Copilot::new();
    copilot.common.skip_permissions = true;

    let args = copilot.build_run_args(true, None);
    assert!(args.contains(&"--allow-all".to_string()));
}

#[test]
fn test_build_run_args_add_dirs() {
    let mut copilot = Copilot::new();
    copilot.common.add_dirs = vec!["/extra".to_string()];

    let args = copilot.build_run_args(true, None);
    assert!(args.contains(&"--add-dir".to_string()));
    assert!(args.contains(&"/extra".to_string()));
}

#[test]
fn test_make_command_without_sandbox() {
    let mut copilot = Copilot::new();
    copilot.common.root = Some("/project".to_string());

    let cmd = copilot.make_command(vec!["-p".to_string(), "hello".to_string()]);
    assert_eq!(cmd.as_std().get_program().to_str().unwrap(), "copilot");
    assert_eq!(
        cmd.as_std().get_current_dir().unwrap().to_str().unwrap(),
        "/project"
    );
}

#[test]
fn test_make_command_with_sandbox() {
    let mut copilot = Copilot::new();
    copilot.common.sandbox = Some(SandboxConfig {
        name: "sandbox-cp".to_string(),
        template: "docker/sandbox-templates:copilot".to_string(),
        workspace: "/workspace".to_string(),
    });

    let cmd = copilot.make_command(vec!["-p".to_string(), "hello".to_string()]);
    assert_eq!(cmd.as_std().get_program().to_str().unwrap(), "docker");
    let args: Vec<&str> = cmd
        .as_std()
        .get_args()
        .map(|a| a.to_str().unwrap())
        .collect();
    assert!(args.contains(&"sandbox"));
    assert!(args.contains(&"run"));
    assert!(args.contains(&"sandbox-cp"));
    assert!(args.contains(&"-p"));
    assert!(args.contains(&"hello"));
}

#[test]
fn test_parse_copilot_assistant_message_event() {
    let line = r#"{"type":"assistant.message","data":{"messageId":"msg-1","content":"hello","toolRequests":[{"toolCallId":"tool-1","name":"view","arguments":{"path":"CLAUDE.md"}}]},"id":"evt-1","timestamp":"2026-01-14T12:41:41.008Z","parentId":null}"#;
    let mut seen = HashSet::new();

    let parsed = parse_copilot_event_line(line, &mut seen).expect("parsed event");

    assert_eq!(parsed.events.len(), 2);
    match &parsed.events[0] {
        LogEventKind::AssistantMessage {
            content,
            message_id,
        } => {
            assert_eq!(content, "hello");
            assert_eq!(message_id.as_deref(), Some("msg-1"));
        }
        other => panic!("unexpected event: {other:?}"),
    }
    match &parsed.events[1] {
        LogEventKind::ToolCall {
            tool_name,
            tool_id,
            input,
            ..
        } => {
            assert_eq!(tool_name, "view");
            assert_eq!(tool_id.as_deref(), Some("tool-1"));
            assert_eq!(
                input
                    .as_ref()
                    .and_then(|value| value.get("path"))
                    .and_then(|value| value.as_str()),
                Some("CLAUDE.md")
            );
        }
        other => panic!("unexpected event: {other:?}"),
    }
}

#[test]
fn test_parse_copilot_tool_result_event() {
    let line = r#"{"type":"tool.execution_complete","data":{"toolCallId":"tool-2","toolName":"bash","success":true,"result":{"content":"ok"}},"id":"evt-2","timestamp":"2026-01-14T12:41:41.008Z","parentId":null}"#;
    let mut seen = HashSet::new();

    let parsed = parse_copilot_event_line(line, &mut seen).expect("parsed event");

    assert_eq!(parsed.events.len(), 1);
    match &parsed.events[0] {
        LogEventKind::ToolResult {
            tool_name,
            tool_id,
            success,
            output,
            error,
            ..
        } => {
            assert_eq!(tool_name.as_deref(), Some("bash"));
            assert_eq!(tool_id.as_deref(), Some("tool-2"));
            assert_eq!(*success, Some(true));
            assert_eq!(output.as_deref(), Some("ok"));
            assert_eq!(error, &None);
        }
        other => panic!("unexpected event: {other:?}"),
    }
}

#[test]
fn test_parse_copilot_session_start_metadata() {
    let line = r#"{"type":"session.start","data":{"sessionId":"session-1","selectedModel":"claude-sonnet-4.5","context":{"cwd":"/repo"}},"id":"evt-3","timestamp":"2026-01-14T12:40:56.938Z","parentId":null}"#;
    let mut seen = HashSet::new();

    let parsed = parse_copilot_event_line(line, &mut seen).expect("parsed event");

    assert_eq!(parsed.provider_session_id.as_deref(), Some("session-1"));
    assert_eq!(parsed.model.as_deref(), Some("claude-sonnet-4.5"));
    assert_eq!(parsed.workspace_path.as_deref(), Some("/repo"));
    assert!(matches!(
        parsed.events.first(),
        Some(LogEventKind::ProviderStatus { .. })
    ));
}

#[test]
fn test_parse_copilot_event_dedupes_ids() {
    let line = r#"{"type":"user.message","data":{"content":"hello"},"id":"evt-4","timestamp":"2026-01-14T12:40:56.938Z","parentId":null}"#;
    let mut seen = HashSet::new();

    assert!(parse_copilot_event_line(line, &mut seen).is_some());
    assert!(parse_copilot_event_line(line, &mut seen).is_none());
}

#[test]
fn test_build_run_args_max_turns() {
    let mut copilot = Copilot::new();
    copilot.common.max_turns = Some(3);

    let args = copilot.build_run_args(false, Some("hello"));
    assert!(args.contains(&"--max-turns".to_string()));
    assert!(args.contains(&"3".to_string()));
}

#[test]
fn test_build_run_args_no_max_turns_by_default() {
    let copilot = Copilot::new();
    let args = copilot.build_run_args(false, Some("hello"));
    assert!(!args.contains(&"--max-turns".to_string()));
}

#[test]
fn test_available_models_includes_gpt_5_4() {
    assert!(AVAILABLE_MODELS.contains(&"gpt-5.4"));
    assert!(AVAILABLE_MODELS.contains(&"gpt-5.4-mini"));
    assert!(AVAILABLE_MODELS.contains(&"gpt-5.3-codex"));
    assert!(AVAILABLE_MODELS.contains(&"gpt-5.2-codex"));
    assert!(AVAILABLE_MODELS.contains(&"gemini-3.1-pro-preview"));
}