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,
)
}
}