agentctl-auth 0.1.0

Unified auth pool and LLM API client for Claude Max Plan, OpenAI, and more
Documentation
//! Test credentials against provider APIs.

use crate::credential::{Credential, TestResult};
use crate::pool::AuthPool;
use anyhow::Result;
use std::time::Instant;

/// Test a single credential against its provider's API.
pub async fn test_credential(name: &str, credential: &Credential) -> TestResult {
    let start = Instant::now();

    let result = match credential.provider.as_str() {
        "anthropic" => test_anthropic(credential).await,
        "openai" => test_openai(credential).await,
        _ => Err(anyhow::anyhow!(
            "Unknown provider: {}",
            credential.provider
        )),
    };

    let latency_ms = start.elapsed().as_millis() as u64;

    match result {
        Ok((success, status_code)) => TestResult {
            credential_name: name.to_string(),
            provider: credential.provider.clone(),
            success,
            status_code: Some(status_code),
            error: if success {
                None
            } else {
                Some(format!("HTTP {}", status_code))
            },
            latency_ms,
        },
        Err(e) => TestResult {
            credential_name: name.to_string(),
            provider: credential.provider.clone(),
            success: false,
            status_code: None,
            error: Some(e.to_string()),
            latency_ms,
        },
    }
}

/// Test all credentials in the pool.
pub async fn test_all(pool: &AuthPool) -> Vec<TestResult> {
    let mut results = Vec::new();
    for (name, cred) in pool.all_credentials() {
        let result = test_credential(name, cred).await;
        results.push(result);
    }
    results
}

/// Test an Anthropic API key.
async fn test_anthropic(credential: &Credential) -> Result<(bool, u16)> {
    let token = credential
        .resolved_token()
        .ok_or_else(|| anyhow::anyhow!("Credential has no token"))?;

    let client = reqwest::Client::new();
    let response = client
        .post("https://api.anthropic.com/v1/messages")
        .header("x-api-key", token)
        .header("anthropic-version", "2023-06-01")
        .header("content-type", "application/json")
        .body(r#"{"model":"claude-sonnet-4-20250514","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}"#)
        .send()
        .await?;

    let status = response.status().as_u16();
    let success = status == 200 || status == 429;
    Ok((success, status))
}

/// Test an OpenAI API key.
async fn test_openai(credential: &Credential) -> Result<(bool, u16)> {
    let token = credential
        .resolved_token()
        .ok_or_else(|| anyhow::anyhow!("Credential has no token"))?;

    let client = reqwest::Client::new();
    let response = client
        .get("https://api.openai.com/v1/models")
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await?;

    let status = response.status().as_u16();
    let success = status == 200;
    Ok((success, status))
}