use async_trait::async_trait;
use mermaid_domain::ChatRequest;
use mermaid_model::models::adapters::anthropic::AnthropicAdapter;
use mermaid_model::models::{Model, ModelConfig, ModelError, Result};
use super::super::ctx::{FinalResponse, StreamContext, StreamEvent};
use super::{ContextSizing, ModelProvider, RejectionCache, resolve_limits_cached};
use mermaid_model::models::ModelCapabilities;
pub const DEFAULT_BASE_URL: &str = "https://api.anthropic.com/v1";
pub const DEFAULT_API_KEY_ENV: &str = "ANTHROPIC_API_KEY";
pub struct AnthropicProvider {
adapter: AnthropicAdapter,
capabilities: ModelCapabilities,
rejections: RejectionCache,
}
impl AnthropicProvider {
pub fn new(api_key: String, model_name: String, base_url: String) -> Result<Self> {
let adapter = AnthropicAdapter::new(api_key, model_name, base_url)?;
let capabilities = adapter.capabilities().clone().with_provider_continuation();
Ok(Self {
adapter,
capabilities,
rejections: RejectionCache::default(),
})
}
}
#[async_trait]
impl ModelProvider for AnthropicProvider {
fn capabilities(&self) -> &ModelCapabilities {
&self.capabilities
}
async fn resolve_context_window(&self, request: &ChatRequest) -> ContextSizing {
let _ = request;
let model = Model::name(&self.adapter).to_string();
let limits =
resolve_limits_cached("anthropic", &model, || self.adapter.fetch_model_limits()).await;
let window = limits.as_ref().and_then(|l| l.max_context_tokens);
self.rejections
.seed("anthropic", &model, self.adapter.param_memory())
.await;
ContextSizing {
model_max: window,
effective: window,
source: None,
max_output: limits.as_ref().and_then(|l| l.max_output_tokens),
compacts_natively: self.adapter.compacts_natively(),
}
}
async fn chat(&self, request: ChatRequest, ctx: StreamContext) -> Result<FinalResponse> {
let config = ModelConfig::from(&request);
let chat_fut = self
.adapter
.chat(&request.messages, &config, Some(ctx.sink.clone()));
let learned_model = Model::name(&self.adapter).to_string();
let memory = self.adapter.param_memory();
self.rejections
.seed("anthropic", &learned_model, memory)
.await;
let response = tokio::select! {
biased;
_ = ctx.token.cancelled() => {
return Err(ModelError::Cancelled);
},
r = chat_fut => {
self.rejections.persist("anthropic", &learned_model, memory).await;
r?
},
};
let usage = response.usage.clone();
let provider_continuation = response.provider_continuation.clone();
let stop_reason = response.stop_reason.clone();
let _ = ctx
.sink
.send(StreamEvent::Done {
usage: usage.clone(),
provider_continuation: provider_continuation.clone(),
stop_reason: stop_reason.clone(),
})
.await;
Ok(FinalResponse {
usage,
provider_continuation,
tool_calls: response.tool_calls.unwrap_or_default(),
stop_reason,
})
}
}