holon 0.14.1

A headless, event-driven runtime for long-lived agents
Documentation
mod anthropic;
mod openai;

use anyhow::{Context, Result};
use reqwest::Client;
#[cfg(test)]
use std::sync::atomic::{AtomicU64, Ordering};
use std::{env, time::Duration};

pub use anthropic::AnthropicProvider;
pub(crate) use openai::OpenAiCompactionPolicy;
#[cfg(test)]
pub(crate) use openai::OpenAiResponsesTransportContract;
#[cfg(test)]
pub(crate) use openai::{
    accumulate_chat_completion_stream_events, build_chat_completion_messages,
    build_chat_completion_request, build_openai_input, build_openai_responses_request,
    classify_openai_chat_completion_error, parse_chat_completion_response, parse_openai_response,
};
pub use openai::{OpenAiChatCompletionsProvider, OpenAiCodexProvider, OpenAiProvider};

const DEFAULT_REQUEST_SEND_TIMEOUT_SECS: u64 = 300;
const DEFAULT_STREAM_IDLE_TIMEOUT_MS: u64 = 300_000;
#[cfg(test)]
static STREAM_IDLE_TIMEOUT_OVERRIDE_MS: AtomicU64 = AtomicU64::new(0);

fn build_http_client() -> Result<Client> {
    let timeout_secs = env::var("HOLON_PROVIDER_HTTP_TIMEOUT_SECS")
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .filter(|value| *value > 0);
    let mut builder = Client::builder();
    if let Some(timeout_secs) = timeout_secs {
        builder = builder.timeout(Duration::from_secs(timeout_secs));
    }
    builder.build().context("failed to build HTTP client")
}

pub(super) fn stream_idle_timeout() -> Duration {
    #[cfg(test)]
    {
        let override_ms = STREAM_IDLE_TIMEOUT_OVERRIDE_MS.load(Ordering::Relaxed);
        if override_ms > 0 {
            return Duration::from_millis(override_ms);
        }
    }
    let timeout_ms = env::var("HOLON_PROVIDER_STREAM_IDLE_TIMEOUT_MS")
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .filter(|value| *value > 0)
        .unwrap_or(DEFAULT_STREAM_IDLE_TIMEOUT_MS);
    Duration::from_millis(timeout_ms)
}

#[cfg(test)]
pub(crate) fn set_stream_idle_timeout_override_for_tests(timeout_ms: Option<u64>) {
    STREAM_IDLE_TIMEOUT_OVERRIDE_MS.store(timeout_ms.unwrap_or(0), Ordering::Relaxed);
}

pub(super) fn request_send_timeout() -> Duration {
    let timeout_secs = env::var("HOLON_PROVIDER_HTTP_TIMEOUT_SECS")
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .filter(|value| *value > 0)
        .unwrap_or(DEFAULT_REQUEST_SEND_TIMEOUT_SECS);
    Duration::from_secs(timeout_secs)
}