mermaid_cli/providers/model/
anthropic.rs1use async_trait::async_trait;
14
15use crate::domain::ChatRequest;
16use crate::models::adapters::anthropic::AnthropicAdapter;
17use crate::models::{Model, ModelConfig, ModelError, Result};
18
19use super::super::capabilities::Capabilities;
20use super::super::ctx::{FinalResponse, StreamContext, StreamEvent};
21use super::{ContextSizing, ModelProvider, resolve_limits_cached};
22
23pub struct AnthropicProvider {
25 adapter: AnthropicAdapter,
26 capabilities: Capabilities,
27}
28
29impl AnthropicProvider {
30 pub fn new(api_key: String, model_name: String, base_url: String) -> Result<Self> {
31 let adapter = AnthropicAdapter::new(api_key, model_name, base_url)?;
32 let capabilities =
33 Capabilities::from_legacy(adapter.capabilities()).with_provider_continuation();
34 Ok(Self {
35 adapter,
36 capabilities,
37 })
38 }
39}
40
41#[async_trait]
42impl ModelProvider for AnthropicProvider {
43 fn capabilities(&self) -> &Capabilities {
44 &self.capabilities
45 }
46
47 async fn resolve_context_window(&self, request: &ChatRequest) -> ContextSizing {
52 let _ = request;
53 let model = Model::name(&self.adapter).to_string();
54 let limits =
55 resolve_limits_cached("anthropic", &model, || self.adapter.fetch_model_limits()).await;
56 let window = limits.as_ref().and_then(|l| l.max_context_tokens);
57 ContextSizing {
58 model_max: window,
59 effective: window,
60 source: None,
61 max_output: limits.as_ref().and_then(|l| l.max_output_tokens),
62 }
63 }
64
65 async fn chat(&self, request: ChatRequest, ctx: StreamContext) -> Result<FinalResponse> {
66 let config = build_model_config(&request);
67 let (relay_tx, relay_handle) = super::stream_bridge::ordered_relay(ctx.sink.clone());
69 let callback = super::stream_bridge::forward_callback(relay_tx.clone());
70 let chat_fut = self
71 .adapter
72 .chat(&request.messages, &config, Some(callback));
73
74 let response = tokio::select! {
75 biased;
76 _ = ctx.token.cancelled() => {
77 return Err(ModelError::Cancelled);
78 },
79 r = chat_fut => r?,
80 };
81
82 let usage = response.usage.clone();
83 let provider_continuation = response.provider_continuation.clone();
84 let stop_reason = response.stop_reason.clone();
85 let _ = relay_tx.send(StreamEvent::Done {
87 usage: usage.clone(),
88 provider_continuation: provider_continuation.clone(),
89 stop_reason: stop_reason.clone(),
90 });
91 drop(relay_tx);
92 crate::utils::join_logged(relay_handle.take(), "stream_relay").await;
93
94 Ok(FinalResponse {
95 usage,
96 provider_continuation,
97 tool_calls: response.tool_calls.unwrap_or_default(),
98 stop_reason,
99 })
100 }
101}
102
103fn build_model_config(request: &ChatRequest) -> ModelConfig {
104 ModelConfig {
105 model: request.model_id.clone(),
106 temperature: request.temperature,
107 max_tokens: request.max_tokens,
108 reasoning: request.reasoning,
109 system_prompt: Some(request.system_prompt.clone()),
110 dynamic_system_suffix: request.instructions.clone(),
111 tools: request.tools.iter().map(|t| t.to_openai_json()).collect(),
112 resolved_context_window: request.resolved_context_window,
113 resolved_max_output: request.resolved_max_output,
114 output_schema: request.output_schema.clone(),
117 ..Default::default()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn build_model_config_maps_fields() {
127 let req = ChatRequest {
128 model_id: "anthropic/claude-opus-4-7".to_string(),
129 messages: vec![],
130 system_prompt: "sys".to_string(),
131 instructions: Some("MERMAID.md content".to_string()),
132 reasoning: crate::models::ReasoningLevel::XHigh,
133 temperature: 0.7,
134 max_tokens: 8192,
135 tools: vec![],
136
137 ollama_num_ctx: None,
138 ollama_allow_ram_offload: None,
139 resolved_context_window: None,
140 resolved_max_output: None,
141 output_schema: None,
142 suppress_auto_compact: false,
143 suppressed_builtin_tools: Vec::new(),
144 };
145 let cfg = build_model_config(&req);
146 assert_eq!(cfg.reasoning, crate::models::ReasoningLevel::XHigh);
147 assert_eq!(cfg.max_tokens, 8192);
148 assert_eq!(
149 cfg.dynamic_system_suffix.as_deref(),
150 Some("MERMAID.md content")
151 );
152 }
153}