magi-code 0.63.3

Repository-aware CLI coding agent for terminal work
Documentation
use crate::{agent::cancellation::AgentCancellation, tools::process};
use serde::Deserialize;
use std::{
    io::ErrorKind,
    process::{Command, Stdio},
    time::Duration,
};

pub(crate) const QUOTA_AXI_BIN: &str = "quota-axi";
pub(crate) const QUOTA_AXI_INSTALL_INSTRUCTIONS: &str = "Install quota-axi, then rerun /usage.\n\nExample:\n  npm install -g quota-axi\n\nExpected command:\n  quota-axi --json";

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct UsageReport {
    pub(crate) generated_at: Option<String>,
    pub(crate) providers: Vec<UsageProviderRow>,
    pub(crate) help: Vec<String>,
    pub(crate) warning: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct UsageProviderRow {
    pub(crate) provider: String,
    pub(crate) label: String,
    pub(crate) source: String,
    pub(crate) plan: Option<String>,
    pub(crate) status: String,
    pub(crate) windows: Vec<UsageWindowRow>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct UsageWindowRow {
    pub(crate) label: String,
    pub(crate) kind: String,
    pub(crate) percent_remaining_basis_points: u16,
    pub(crate) percent_used_basis_points: Option<u16>,
    pub(crate) reset_text: Option<String>,
    pub(crate) resets_at: Option<String>,
    pub(crate) spent_usd: Option<String>,
    pub(crate) limit_usd: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum UsageLoadResult {
    Loaded(UsageReport),
    MissingCli,
    Error(String),
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct QuotaAxiResponse {
    generated_at: Option<String>,
    schema_version: u64,
    providers: Vec<QuotaAxiProvider>,
    #[serde(default)]
    help: Vec<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct QuotaAxiProvider {
    provider: String,
    label: String,
    source: String,
    plan: Option<String>,
    #[serde(default)]
    windows: Vec<QuotaAxiWindow>,
    state: QuotaAxiState,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct QuotaAxiWindow {
    label: String,
    kind: String,
    percent_used: Option<f64>,
    percent_remaining: Option<f64>,
    reset_text: Option<String>,
    resets_at: Option<String>,
    spent_usd: Option<f64>,
    limit_usd: Option<f64>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct QuotaAxiState {
    status: String,
}

pub(crate) fn load_usage() -> UsageLoadResult {
    let mut command = Command::new(QUOTA_AXI_BIN);
    command
        .arg("--json")
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    #[cfg(unix)]
    {
        use std::os::unix::process::CommandExt;
        command.process_group(0);
    }
    let child = match command.spawn() {
        Ok(child) => child,
        Err(error) if error.kind() == ErrorKind::NotFound => return UsageLoadResult::MissingCli,
        Err(error) => return UsageLoadResult::Error(format!("failed to start quota-axi: {error}")),
    };
    let output = match process::run_bounded_child_process(
        child,
        process::BoundedChildProcessLimits {
            stdout_max_bytes: 256 * 1024,
            stderr_max_bytes: 64 * 1024,
            timeout: Duration::from_secs(15),
            poll_interval: Duration::from_millis(25),
        },
        &AgentCancellation::default(),
    ) {
        Ok(output) => output,
        Err(error) => return UsageLoadResult::Error(format!("failed to run quota-axi: {error}")),
    };
    if output.timed_out {
        return UsageLoadResult::Error("quota-axi timed out after 15s".to_string());
    }
    if output.stdout_truncated || output.stderr_truncated {
        return UsageLoadResult::Error("quota-axi output exceeded safety limit".to_string());
    }
    let code = output.status.and_then(|status| status.code()).unwrap_or(-1);
    let mut result = match code {
        0 | 1 => parse_usage_report(&output.stdout)
            .map(UsageLoadResult::Loaded)
            .unwrap_or_else(UsageLoadResult::Error),
        2 => UsageLoadResult::Error(short_error_with_snippet(
            "quota-axi usage error; expected command: quota-axi --json",
            &output.stderr,
            &output.stdout,
        )),
        127 => UsageLoadResult::MissingCli,
        other => UsageLoadResult::Error(format!("quota-axi failed with exit {other}")),
    };
    if let UsageLoadResult::Error(message) = &mut result
        && let Some(warning) = output.cleanup_warning.filter(|warning| !warning.is_empty())
    {
        message.push_str("; ");
        message.push_str(&warning);
    }
    result
}

pub(crate) fn parse_usage_report(stdout: &str) -> Result<UsageReport, String> {
    let response: QuotaAxiResponse = serde_json::from_str(stdout)
        .map_err(|_| "quota-axi returned non-JSON output".to_string())?;
    if response.schema_version != 2 {
        return Err(format!(
            "unsupported quota-axi schemaVersion {}; expected 2",
            response.schema_version
        ));
    }
    let providers = response
        .providers
        .into_iter()
        .filter_map(provider_row)
        .collect::<Vec<_>>();
    Ok(UsageReport {
        generated_at: response.generated_at,
        providers,
        help: response.help,
        warning: None,
    })
}

fn provider_row(provider: QuotaAxiProvider) -> Option<UsageProviderRow> {
    let windows = provider
        .windows
        .into_iter()
        .filter_map(window_row)
        .collect::<Vec<_>>();
    (!windows.is_empty()).then_some(UsageProviderRow {
        provider: provider.provider,
        label: provider.label,
        source: provider.source,
        plan: provider.plan,
        status: provider.state.status,
        windows,
    })
}

fn window_row(window: QuotaAxiWindow) -> Option<UsageWindowRow> {
    let percent_remaining = window.percent_remaining.filter(|value| value.is_finite())?;
    Some(UsageWindowRow {
        label: window.label,
        kind: window.kind,
        percent_remaining_basis_points: percent_to_basis_points(percent_remaining),
        percent_used_basis_points: window
            .percent_used
            .filter(|value| value.is_finite())
            .map(percent_to_basis_points),
        reset_text: window.reset_text,
        resets_at: window.resets_at,
        spent_usd: window
            .spent_usd
            .filter(|value| value.is_finite())
            .map(format_usd),
        limit_usd: window
            .limit_usd
            .filter(|value| value.is_finite())
            .map(format_usd),
    })
}

fn percent_to_basis_points(value: f64) -> u16 {
    (value * 100.0).round().clamp(0.0, f64::from(u16::MAX)) as u16
}

fn format_usd(value: f64) -> String {
    format!("${value:.2}")
}

pub(crate) fn format_percent_basis_points(basis_points: u16) -> String {
    let whole = basis_points / 100;
    let tenth = (basis_points % 100) / 10;
    if tenth == 0 {
        format!("{whole}%")
    } else {
        format!("{whole}.{tenth}%")
    }
}

/// Plain-text content for the usage modal states that render as a single
/// wrapped block of text. Returns `None` for the loaded-with-providers state,
/// which renders as a mix of text and gauge rows instead. Shared by the
/// renderer and the scroll-overflow calculation so the two never disagree
/// about how many visual rows the content occupies.
pub(crate) fn usage_text_content(
    loading: bool,
    result: Option<&UsageLoadResult>,
) -> Option<String> {
    if loading || result.is_none() {
        return Some("Loading provider usage…".to_string());
    }
    match result.unwrap() {
        UsageLoadResult::MissingCli => Some(QUOTA_AXI_INSTALL_INSTRUCTIONS.to_string()),
        UsageLoadResult::Error(message) => Some(format!(
            "Could not load usage: {message}\n\nExpected command: quota-axi --json"
        )),
        UsageLoadResult::Loaded(report) if report.providers.is_empty() => {
            let mut text = "No provider usage data available. Run provider login/setup commands, then rerun /usage.".to_string();
            if !report.help.is_empty() {
                text.push_str("\n\n");
                text.push_str(&report.help.join("\n"));
            }
            Some(text)
        }
        UsageLoadResult::Loaded(_) => None,
    }
}

pub(crate) fn usage_visual_rows(
    loading: bool,
    result: Option<&UsageLoadResult>,
    wrap_width: u16,
) -> usize {
    if let Some(text) = usage_text_content(loading, result) {
        return wrapped_rows(&text, wrap_width);
    }
    let Some(UsageLoadResult::Loaded(report)) = result else {
        return 0;
    };
    usize::from(report.generated_at.is_some())
        + report
            .providers
            .iter()
            .map(|provider| 1 + provider.windows.len() * 2 + 1)
            .sum::<usize>()
}

fn wrapped_rows(text: &str, wrap_width: u16) -> usize {
    crate::tui::transcript::ratatui_wrapped_visual_rows(text, wrap_width.max(1))
}

fn short_error_with_snippet(prefix: &str, stderr: &str, stdout: &str) -> String {
    let snippet = stderr
        .trim()
        .lines()
        .next()
        .or_else(|| stdout.trim().lines().next());
    snippet.map_or_else(|| prefix.to_string(), |line| format!("{prefix}: {line}"))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sample_json(extra_provider: &str) -> String {
        format!(
            r#"{{
  "schemaVersion": 2,
  "generatedAt": "2026-01-01T00:00:00Z",
  "help": ["run setup"],
  "providers": [
    {{
      "provider": "openai-codex",
      "label": "OpenAI Codex",
      "source": "oauth",
      "plan": "Pro",
      "state": {{"status": "fresh"}},
      "windows": [{{
        "label": "5h",
        "kind": "rolling",
        "percentUsed": 42.5,
        "percentRemaining": 57.5,
        "resetText": "in 2h",
        "spentUsd": 1.5,
        "limitUsd": 10
      }}]
    }}{extra_provider}
  ]
}}"#
        )
    }

    #[test]
    fn parses_schema_v2_sample_with_percent_remaining() {
        let report = parse_usage_report(&sample_json("")).unwrap();
        assert_eq!(report.generated_at.as_deref(), Some("2026-01-01T00:00:00Z"));
        assert_eq!(report.providers.len(), 1);
        let provider = &report.providers[0];
        assert_eq!(provider.label, "OpenAI Codex");
        assert_eq!(provider.windows[0].percent_remaining_basis_points, 5750);
        assert_eq!(provider.windows[0].percent_used_basis_points, Some(4250));
        assert_eq!(provider.windows[0].spent_usd.as_deref(), Some("$1.50"));
        assert_eq!(provider.windows[0].limit_usd.as_deref(), Some("$10.00"));
    }

    #[test]
    fn filters_auth_required_provider_with_empty_windows() {
        let extra = r#",
    {
      "provider": "anthropic",
      "label": "Anthropic",
      "source": "none",
      "state": {"status": "auth_required"},
      "windows": []
    }"#;
        let report = parse_usage_report(&sample_json(extra)).unwrap();
        assert_eq!(report.providers.len(), 1);
        assert_eq!(report.providers[0].provider, "openai-codex");
    }

    #[test]
    fn excludes_window_without_percent_remaining() {
        let json = r#"{"schemaVersion":2,"providers":[{"provider":"p","label":"P","source":"s","state":{"status":"fresh"},"windows":[{"label":"daily","kind":"fixed","percentUsed":10}]}]}"#;
        let report = parse_usage_report(json).unwrap();
        assert!(report.providers.is_empty());
    }

    #[test]
    fn rejects_unsupported_schema_version() {
        let error = parse_usage_report(r#"{"schemaVersion":3,"providers":[]}"#).unwrap_err();
        assert_eq!(error, "unsupported quota-axi schemaVersion 3; expected 2");
    }

    #[test]
    fn maps_non_json_stdout_to_parse_error() {
        let error = parse_usage_report("not json").unwrap_err();
        assert_eq!(error, "quota-axi returned non-JSON output");
    }

    #[test]
    fn usage_visual_rows_matches_wrapped_row_count_for_long_error_line() {
        let long_line = "x".repeat(200);
        let result = UsageLoadResult::Error(long_line);
        let wrap_width = 20;
        let expected = wrapped_rows(
            &usage_text_content(false, Some(&result)).unwrap(),
            wrap_width,
        );
        assert_eq!(
            usage_visual_rows(false, Some(&result), wrap_width),
            expected
        );
        assert!(expected > 1);
    }
}