dot-ai 0.6.1

A minimal AI agent that lives in your terminal
Documentation
use std::time::{SystemTime, UNIX_EPOCH};

use anyhow::Context;

pub enum AnthropicAuth {
    ApiKey(String),
    OAuth {
        access_token: String,
        refresh_token: String,
        expires_at: i64,
    },
}

pub(super) struct AuthResolved {
    pub header_name: String,
    pub header_value: String,
    pub is_oauth: bool,
}

pub(super) async fn refresh_oauth_token(
    client: &reqwest::Client,
    refresh_token: &str,
) -> anyhow::Result<(String, i64)> {
    let resp = client
        .post("https://console.anthropic.com/v1/oauth/token")
        .json(&serde_json::json!({
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
            "client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
        }))
        .send()
        .await
        .context("Failed to send OAuth refresh request")?;

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();
        return Err(anyhow::anyhow!("OAuth refresh failed {status}: {body}"));
    }

    let data: serde_json::Value = resp
        .json()
        .await
        .context("Failed to parse OAuth refresh response")?;
    let access_token = data["access_token"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("No access_token in refresh response"))?
        .to_string();
    let expires_in = data["expires_in"].as_i64().unwrap_or(3600);
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64;
    let expires_at = now + expires_in;

    Ok((access_token, expires_at))
}