magi-code 0.64.0

Repository-aware CLI coding agent for terminal work
Documentation
pub(crate) mod auth;
pub(crate) mod body;
pub(crate) mod headers;

#[cfg(test)]
mod tests;

use crate::{
    agent::cancellation::AgentCancellation,
    config::AnthropicCacheTtl,
    providers::{
        HttpRequest, HttpTransport, Provider, ProviderEvent, ProviderRequest,
        anthropic::AnthropicStreamParser, openai_stream::stream_with_transport_parser,
    },
};
use auth::ClaudeCodeAuth;
pub(crate) use body::claude_code_messages_body;
use std::{fmt, sync::Mutex};

fn lock_auth(
    auth: &Mutex<ClaudeCodeAuth>,
) -> anyhow::Result<std::sync::MutexGuard<'_, ClaudeCodeAuth>> {
    auth.lock()
        .map_err(|_| anyhow::anyhow!("claude-code auth lock was poisoned"))
}

pub struct ClaudeCodeProvider<T> {
    model: String,
    auth: Mutex<ClaudeCodeAuth>,
    cache_ttl: Option<AnthropicCacheTtl>,
    max_output_tokens: Option<u64>,
    transport: T,
}

impl<T: fmt::Debug> fmt::Debug for ClaudeCodeProvider<T> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("ClaudeCodeProvider")
            .field("model", &self.model)
            .field("auth", &"<redacted>")
            .field("transport", &self.transport)
            .field("cache_ttl", &self.cache_ttl)
            .field("max_output_tokens", &self.max_output_tokens)
            .finish()
    }
}

impl<T> ClaudeCodeProvider<T> {
    pub(crate) fn new(model: impl Into<String>, auth: ClaudeCodeAuth, transport: T) -> Self {
        Self {
            model: model.into(),
            auth: Mutex::new(auth),
            cache_ttl: None,
            max_output_tokens: None,
            transport,
        }
    }

    pub(crate) fn with_cache_ttl(mut self, cache_ttl: Option<AnthropicCacheTtl>) -> Self {
        self.cache_ttl = cache_ttl;
        self
    }

    pub(crate) fn with_max_output_tokens(mut self, max: Option<u64>) -> Self {
        self.max_output_tokens = max;
        self
    }

    pub(crate) fn build_http_request(
        &self,
        request: &ProviderRequest,
    ) -> anyhow::Result<HttpRequest> {
        let auth = lock_auth(&self.auth)?;
        let access_mode = auth.access_mode();
        let is_oauth = access_mode.is_oauth();
        Ok(HttpRequest {
            method: "POST".to_string(),
            url: body::messages_url(),
            headers: headers::claude_code_headers(access_mode),
            body: body::claude_code_messages_body(
                &self.model,
                request,
                is_oauth,
                self.cache_ttl,
                self.max_output_tokens,
            ),
        })
    }
}

impl<T: HttpTransport + Send + Sync> Provider for ClaudeCodeProvider<T> {
    #[cfg(test)]
    fn anthropic_cache_ttl_for_test(&self) -> Option<AnthropicCacheTtl> {
        self.cache_ttl
    }

    fn stream_cancellable(
        &self,
        request: ProviderRequest,
        cancellation: &AgentCancellation,
        on_event: &mut dyn FnMut(ProviderEvent) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        cancellation.check()?;
        let refresh = {
            let mut auth = lock_auth(&self.auth)?;
            auth.resync_and_refresh_snapshot_if_needed()?
        };
        if let Some(refresh) = refresh {
            let refreshed = refresh.refresh(cancellation)?;
            {
                let mut auth = lock_auth(&self.auth)?;
                auth.commit_refreshed_snapshot_if_current(&refresh, refreshed)?;
            }
        }
        cancellation.check()?;
        let semantic_progress_timeout = request.semantic_progress_timeout_or_default();
        stream_with_transport_parser(
            &self.transport,
            self.build_http_request(&request)?,
            cancellation,
            semantic_progress_timeout,
            AnthropicStreamParser::default(),
            on_event,
        )
    }
}