saya-agent 0.1.2

Agentic LLM loop and OpenAI-compatible provider clients for SAYA CLI.
Documentation
use crate::{CancellationToken, ProviderError};
use reqwest::{RequestBuilder, Response, StatusCode};
use std::time::Duration;

pub(super) async fn send_stream(
    mut build: impl FnMut() -> RequestBuilder,
    delays: &[Duration],
    cancellation: &CancellationToken,
) -> Result<Response, ProviderError> {
    for attempt in 0..=delays.len() {
        let response = tokio::select! {
            _ = cancellation.cancelled() => return Err(ProviderError::Cancelled),
            response = build().send() => response,
        };
        match response {
            Ok(response) if response.status().is_success() => return Ok(response),
            Ok(response) if retryable(response.status()) && attempt < delays.len() => {
                wait(delays[attempt], cancellation).await?;
            }
            Ok(response) => {
                return Err(ProviderError::Request(format!(
                    "HTTP {}",
                    response.status().as_u16()
                )));
            }
            Err(_) if attempt < delays.len() => wait(delays[attempt], cancellation).await?,
            Err(_) => return Err(ProviderError::Request("network request failed".into())),
        }
    }
    Err(ProviderError::Request("network request failed".into()))
}

async fn wait(delay: Duration, cancellation: &CancellationToken) -> Result<(), ProviderError> {
    tokio::select! {
        _ = cancellation.cancelled() => Err(ProviderError::Cancelled),
        _ = tokio::time::sleep(delay) => Ok(()),
    }
}

fn retryable(status: StatusCode) -> bool {
    status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error()
}