1use std::collections::HashMap;
2
3use derive_builder::Builder;
4use futures_util::{StreamExt, stream::BoxStream};
5use reqwest::Client as HttpClient;
6use serde::{Deserialize, Deserializer, Serialize, de};
7use serde_json::{Value, json};
8
9use crate::{
10 api::chat::{CacheControl, Plugin, TraceOptions},
11 error::OpenRouterError,
12 strip_option_vec_setter,
13 transport::{
14 request as transport_request, response as transport_response, sse::response_lines,
15 },
16 types::{AnthropicCacheCreation, OpenRouterExperimentalMetadata, ProviderPreferences},
17 utils::parse_sse_frames,
18};
19
20#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
22#[non_exhaustive]
23#[serde(rename_all = "lowercase")]
24pub enum AnthropicRole {
25 User,
26 Assistant,
27 System,
28}
29
30#[derive(Serialize, Deserialize, Debug, Clone)]
32#[non_exhaustive]
33pub struct AnthropicSystemTextBlock {
34 #[serde(rename = "type")]
35 pub block_type: AnthropicSystemTextBlockType,
36 pub text: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub citations: Option<Vec<Value>>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub cache_control: Option<CacheControl>,
41 #[serde(flatten)]
42 pub extra: HashMap<String, Value>,
43}
44
45impl AnthropicSystemTextBlock {
46 pub fn text(text: impl Into<String>) -> Self {
47 Self {
48 block_type: AnthropicSystemTextBlockType::Text,
49 text: text.into(),
50 citations: None,
51 cache_control: None,
52 extra: HashMap::new(),
53 }
54 }
55}
56
57#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
59#[non_exhaustive]
60#[serde(rename_all = "snake_case")]
61pub enum AnthropicSystemTextBlockType {
62 Text,
63}
64
65#[derive(Serialize, Deserialize, Debug, Clone)]
67#[non_exhaustive]
68#[serde(untagged)]
69pub enum AnthropicSystemPrompt {
70 Text(String),
71 Blocks(Vec<AnthropicSystemTextBlock>),
72}
73
74#[derive(Serialize, Deserialize, Debug, Clone)]
76#[non_exhaustive]
77#[serde(untagged)]
78pub enum AnthropicMessageContent {
79 Text(String),
80 Parts(Vec<AnthropicContentPart>),
81}
82
83impl From<String> for AnthropicMessageContent {
84 fn from(value: String) -> Self {
85 Self::Text(value)
86 }
87}
88
89impl From<&str> for AnthropicMessageContent {
90 fn from(value: &str) -> Self {
91 Self::Text(value.to_string())
92 }
93}
94
95impl From<Vec<AnthropicContentPart>> for AnthropicMessageContent {
96 fn from(value: Vec<AnthropicContentPart>) -> Self {
97 Self::Parts(value)
98 }
99}
100
101#[derive(Serialize, Deserialize, Debug, Clone)]
103#[non_exhaustive]
104#[serde(tag = "type", rename_all = "snake_case")]
105pub enum AnthropicContentPart {
106 Text {
107 text: String,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 citations: Option<Vec<Value>>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 cache_control: Option<CacheControl>,
112 },
113 Image {
114 source: Value,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 cache_control: Option<CacheControl>,
117 },
118 Document {
119 source: Value,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 title: Option<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 context: Option<String>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 citations: Option<Vec<Value>>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 cache_control: Option<CacheControl>,
128 },
129 ToolUse {
130 id: String,
131 name: String,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 input: Option<Value>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 cache_control: Option<CacheControl>,
136 },
137 ToolResult {
138 tool_use_id: String,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 content: Option<AnthropicMessageContent>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 is_error: Option<bool>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 cache_control: Option<CacheControl>,
145 },
146 Thinking {
147 thinking: String,
148 signature: String,
149 },
150 RedactedThinking {
151 data: String,
152 },
153 ServerToolUse {
154 id: String,
155 name: String,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 input: Option<Value>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 cache_control: Option<CacheControl>,
160 },
161 WebSearchToolResult {
162 tool_use_id: String,
163 content: Value,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 cache_control: Option<CacheControl>,
166 },
167 SearchResult {
168 source: String,
169 title: String,
170 content: Value,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 citations: Option<Vec<Value>>,
173 #[serde(skip_serializing_if = "Option::is_none")]
174 cache_control: Option<CacheControl>,
175 },
176 Compaction {
177 content: Option<String>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 encrypted_content: Option<String>,
180 },
181 AdvisorToolResult {
182 tool_use_id: String,
183 content: Value,
184 },
185 ToolReference {
186 tool_name: String,
187 },
188 ToolAddition {
189 tool: Value,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 cache_control: Option<CacheControl>,
192 },
193 ToolRemoval {
194 tool: Value,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 cache_control: Option<CacheControl>,
197 },
198}
199
200impl AnthropicContentPart {
201 pub fn text(text: impl Into<String>) -> Self {
202 Self::Text {
203 text: text.into(),
204 citations: None,
205 cache_control: None,
206 }
207 }
208
209 pub fn image_url(url: impl Into<String>) -> Self {
210 Self::Image {
211 source: json!({
212 "type": "url",
213 "url": url.into()
214 }),
215 cache_control: None,
216 }
217 }
218
219 pub fn image_base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
220 Self::Image {
221 source: json!({
222 "type": "base64",
223 "media_type": media_type.into(),
224 "data": data.into()
225 }),
226 cache_control: None,
227 }
228 }
229
230 pub fn document_url(url: impl Into<String>) -> Self {
231 Self::Document {
232 source: json!({
233 "type": "url",
234 "url": url.into()
235 }),
236 title: None,
237 context: None,
238 citations: None,
239 cache_control: None,
240 }
241 }
242
243 pub fn document_file_id(file_id: impl Into<String>) -> Self {
244 Self::Document {
245 source: json!({
246 "type": "file",
247 "file_id": file_id.into()
248 }),
249 title: None,
250 context: None,
251 citations: None,
252 cache_control: None,
253 }
254 }
255
256 pub fn tool_use(
257 id: impl Into<String>,
258 name: impl Into<String>,
259 input: impl Into<Value>,
260 ) -> Self {
261 Self::ToolUse {
262 id: id.into(),
263 name: name.into(),
264 input: Some(input.into()),
265 cache_control: None,
266 }
267 }
268
269 pub fn tool_result(
270 tool_use_id: impl Into<String>,
271 content: impl Into<AnthropicMessageContent>,
272 ) -> Self {
273 Self::ToolResult {
274 tool_use_id: tool_use_id.into(),
275 content: Some(content.into()),
276 is_error: None,
277 cache_control: None,
278 }
279 }
280}
281
282#[derive(Serialize, Deserialize, Debug, Clone)]
284#[non_exhaustive]
285pub struct AnthropicMessage {
286 pub role: AnthropicRole,
287 pub content: AnthropicMessageContent,
288}
289
290impl AnthropicMessage {
291 pub fn new(role: AnthropicRole, content: impl Into<AnthropicMessageContent>) -> Self {
292 Self {
293 role,
294 content: content.into(),
295 }
296 }
297
298 pub fn user(content: impl Into<AnthropicMessageContent>) -> Self {
299 Self::new(AnthropicRole::User, content)
300 }
301
302 pub fn assistant(content: impl Into<AnthropicMessageContent>) -> Self {
303 Self::new(AnthropicRole::Assistant, content)
304 }
305
306 pub fn system(content: impl Into<AnthropicMessageContent>) -> Self {
307 Self::new(AnthropicRole::System, content)
308 }
309
310 pub fn with_parts(role: AnthropicRole, parts: Vec<AnthropicContentPart>) -> Self {
311 Self {
312 role,
313 content: AnthropicMessageContent::Parts(parts),
314 }
315 }
316}
317
318#[derive(Serialize, Deserialize, Debug, Clone, Default)]
320#[non_exhaustive]
321pub struct AnthropicMessagesMetadata {
322 #[serde(skip_serializing_if = "Option::is_none")]
323 pub user_id: Option<String>,
324 #[serde(flatten)]
325 pub extra: HashMap<String, Value>,
326}
327
328impl AnthropicMessagesMetadata {
329 pub fn with_user_id(user_id: impl Into<String>) -> Self {
330 Self {
331 user_id: Some(user_id.into()),
332 extra: HashMap::new(),
333 }
334 }
335}
336
337#[derive(Serialize, Deserialize, Debug, Clone)]
339#[non_exhaustive]
340pub struct AnthropicTool {
341 pub name: String,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub description: Option<String>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub input_schema: Option<Value>,
346 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
347 pub tool_type: Option<String>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub cache_control: Option<CacheControl>,
350 #[serde(flatten)]
351 pub extra: HashMap<String, Value>,
352}
353
354impl AnthropicTool {
355 pub fn custom(
356 name: impl Into<String>,
357 description: impl Into<String>,
358 input_schema: impl Into<Value>,
359 ) -> Self {
360 Self {
361 name: name.into(),
362 description: Some(description.into()),
363 input_schema: Some(input_schema.into()),
364 tool_type: Some("custom".to_string()),
365 cache_control: None,
366 extra: HashMap::new(),
367 }
368 }
369
370 pub fn hosted(tool_type: impl Into<String>, name: impl Into<String>) -> Self {
371 Self {
372 name: name.into(),
373 description: None,
374 input_schema: None,
375 tool_type: Some(tool_type.into()),
376 cache_control: None,
377 extra: HashMap::new(),
378 }
379 }
380
381 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
382 self.extra.insert(key.into(), value.into());
383 self
384 }
385}
386
387#[derive(Serialize, Deserialize, Debug, Clone)]
389#[non_exhaustive]
390#[serde(tag = "type", rename_all = "snake_case")]
391pub enum AnthropicToolChoice {
392 Auto {
393 #[serde(skip_serializing_if = "Option::is_none")]
394 disable_parallel_tool_use: Option<bool>,
395 },
396 Any {
397 #[serde(skip_serializing_if = "Option::is_none")]
398 disable_parallel_tool_use: Option<bool>,
399 },
400 None,
401 Tool {
402 name: String,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 disable_parallel_tool_use: Option<bool>,
405 },
406}
407
408impl AnthropicToolChoice {
409 pub fn auto() -> Self {
410 Self::Auto {
411 disable_parallel_tool_use: None,
412 }
413 }
414
415 pub fn any() -> Self {
416 Self::Any {
417 disable_parallel_tool_use: None,
418 }
419 }
420
421 pub fn none() -> Self {
422 Self::None
423 }
424
425 pub fn tool(name: impl Into<String>) -> Self {
426 Self::Tool {
427 name: name.into(),
428 disable_parallel_tool_use: None,
429 }
430 }
431}
432
433#[derive(Serialize, Deserialize, Debug, Clone)]
435#[non_exhaustive]
436#[serde(tag = "type", rename_all = "snake_case")]
437pub enum AnthropicThinking {
438 Enabled { budget_tokens: u32 },
439 Disabled,
440 Adaptive,
441}
442
443impl AnthropicThinking {
444 pub fn enabled(budget_tokens: u32) -> Self {
445 Self::Enabled { budget_tokens }
446 }
447
448 pub fn disabled() -> Self {
449 Self::Disabled
450 }
451
452 pub fn adaptive() -> Self {
453 Self::Adaptive
454 }
455}
456
457#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
459#[non_exhaustive]
460#[serde(rename_all = "lowercase")]
461pub enum AnthropicOutputEffort {
462 Low,
463 Medium,
464 High,
465 Max,
466}
467
468#[derive(Serialize, Deserialize, Debug, Clone, Default)]
470#[non_exhaustive]
471pub struct AnthropicOutputConfig {
472 #[serde(skip_serializing_if = "Option::is_none")]
473 pub effort: Option<AnthropicOutputEffort>,
474}
475
476impl AnthropicOutputConfig {
477 pub fn with_effort(effort: AnthropicOutputEffort) -> Self {
478 Self {
479 effort: Some(effort),
480 }
481 }
482}
483
484#[derive(Debug, Clone, Builder)]
486#[builder(build_fn(error = "OpenRouterError"))]
487#[non_exhaustive]
488pub struct AnthropicMessagesRequest {
489 #[builder(setter(into))]
490 model: String,
491
492 max_tokens: u32,
493
494 messages: Vec<AnthropicMessage>,
495
496 #[builder(setter(strip_option), default)]
497 system: Option<AnthropicSystemPrompt>,
498
499 #[builder(setter(strip_option), default)]
500 metadata: Option<AnthropicMessagesMetadata>,
501
502 #[builder(setter(custom), default)]
503 stop_sequences: Option<Vec<String>>,
504
505 #[builder(setter(skip), default)]
506 stream: Option<bool>,
507
508 #[builder(setter(strip_option), default)]
509 experimental_metadata: Option<OpenRouterExperimentalMetadata>,
510
511 #[builder(setter(strip_option), default)]
512 temperature: Option<f64>,
513
514 #[builder(setter(strip_option), default)]
515 top_p: Option<f64>,
516
517 #[builder(setter(strip_option), default)]
518 top_k: Option<u32>,
519
520 #[builder(setter(custom), default)]
521 tools: Option<Vec<AnthropicTool>>,
522
523 #[builder(setter(custom), default)]
524 server_tools: Option<Vec<crate::types::ServerTool>>,
525
526 #[builder(setter(strip_option), default)]
527 tool_choice: Option<AnthropicToolChoice>,
528
529 #[builder(setter(strip_option), default)]
530 thinking: Option<AnthropicThinking>,
531
532 #[builder(setter(into, strip_option), default)]
533 service_tier: Option<String>,
534
535 #[builder(setter(strip_option), default)]
536 provider: Option<ProviderPreferences>,
537
538 #[builder(setter(custom), default)]
539 plugins: Option<Vec<Plugin>>,
540
541 #[builder(setter(into, strip_option), default)]
542 route: Option<String>,
543
544 #[builder(setter(into, strip_option), default)]
545 user: Option<String>,
546
547 #[builder(setter(into, strip_option), default)]
548 session_id: Option<String>,
549
550 #[builder(setter(strip_option), default)]
551 trace: Option<TraceOptions>,
552
553 #[builder(setter(custom), default)]
554 models: Option<Vec<String>>,
555
556 #[builder(setter(strip_option), default)]
557 output_config: Option<AnthropicOutputConfig>,
558}
559
560#[derive(Deserialize)]
561struct AnthropicMessagesRequestWire {
562 model: String,
563 max_tokens: u32,
564 messages: Vec<AnthropicMessage>,
565 system: Option<AnthropicSystemPrompt>,
566 metadata: Option<AnthropicMessagesMetadata>,
567 stop_sequences: Option<Vec<String>>,
568 stream: Option<bool>,
569 #[serde(skip)]
570 experimental_metadata: Option<OpenRouterExperimentalMetadata>,
571 temperature: Option<f64>,
572 top_p: Option<f64>,
573 top_k: Option<u32>,
574 tools: Option<Vec<Value>>,
575 tool_choice: Option<AnthropicToolChoice>,
576 thinking: Option<AnthropicThinking>,
577 service_tier: Option<String>,
578 provider: Option<ProviderPreferences>,
579 plugins: Option<Vec<Plugin>>,
580 route: Option<String>,
581 user: Option<String>,
582 session_id: Option<String>,
583 trace: Option<TraceOptions>,
584 models: Option<Vec<String>>,
585 output_config: Option<AnthropicOutputConfig>,
586}
587
588type SplitAnthropicMessageTools = (
589 Option<Vec<AnthropicTool>>,
590 Option<Vec<crate::types::ServerTool>>,
591);
592
593impl<'de> Deserialize<'de> for AnthropicMessagesRequest {
594 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
595 where
596 D: Deserializer<'de>,
597 {
598 let wire = AnthropicMessagesRequestWire::deserialize(deserializer)?;
599 let (tools, server_tools) = split_anthropic_message_tools(wire.tools).map_err(|error| {
600 de::Error::custom(format!("invalid Anthropic messages tools entry: {error}"))
601 })?;
602
603 Ok(Self {
604 model: wire.model,
605 max_tokens: wire.max_tokens,
606 messages: wire.messages,
607 system: wire.system,
608 metadata: wire.metadata,
609 stop_sequences: wire.stop_sequences,
610 stream: wire.stream,
611 experimental_metadata: wire.experimental_metadata,
612 temperature: wire.temperature,
613 top_p: wire.top_p,
614 top_k: wire.top_k,
615 tools,
616 server_tools,
617 tool_choice: wire.tool_choice,
618 thinking: wire.thinking,
619 service_tier: wire.service_tier,
620 provider: wire.provider,
621 plugins: wire.plugins,
622 route: wire.route,
623 user: wire.user,
624 session_id: wire.session_id,
625 trace: wire.trace,
626 models: wire.models,
627 output_config: wire.output_config,
628 })
629 }
630}
631
632fn split_anthropic_message_tools(
633 tools: Option<Vec<Value>>,
634) -> Result<SplitAnthropicMessageTools, serde_json::Error> {
635 let Some(values) = tools else {
636 return Ok((None, None));
637 };
638 let was_empty = values.is_empty();
639 let mut anthropic_tools = Vec::new();
640 let mut server_tools = Vec::new();
641
642 for value in values {
643 if value.get("name").is_some() {
644 anthropic_tools.push(serde_json::from_value(value)?);
645 } else if crate::types::ServerTool::is_server_tool_value(&value) {
646 server_tools.push(serde_json::from_value(value)?);
647 } else {
648 anthropic_tools.push(serde_json::from_value(value)?);
649 }
650 }
651
652 let tools = if anthropic_tools.is_empty() && !was_empty {
653 None
654 } else {
655 Some(anthropic_tools)
656 };
657 let server_tools = if server_tools.is_empty() {
658 None
659 } else {
660 Some(server_tools)
661 };
662
663 Ok((tools, server_tools))
664}
665
666fn insert_json_field<T, E>(
667 map: &mut serde_json::Map<String, Value>,
668 key: &str,
669 value: &T,
670) -> Result<(), E>
671where
672 T: Serialize,
673 E: serde::ser::Error,
674{
675 map.insert(
676 key.to_string(),
677 serde_json::to_value(value).map_err(E::custom)?,
678 );
679 Ok(())
680}
681
682fn insert_json_option<T, E>(
683 map: &mut serde_json::Map<String, Value>,
684 key: &str,
685 value: &Option<T>,
686) -> Result<(), E>
687where
688 T: Serialize,
689 E: serde::ser::Error,
690{
691 if let Some(value) = value {
692 insert_json_field::<T, E>(map, key, value)?;
693 }
694 Ok(())
695}
696
697impl Serialize for AnthropicMessagesRequest {
698 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
699 where
700 S: serde::Serializer,
701 {
702 let mut map = serde_json::Map::new();
703
704 insert_json_field::<_, S::Error>(&mut map, "model", &self.model)?;
705 insert_json_field::<_, S::Error>(&mut map, "max_tokens", &self.max_tokens)?;
706 insert_json_field::<_, S::Error>(&mut map, "messages", &self.messages)?;
707 insert_json_option::<_, S::Error>(&mut map, "system", &self.system)?;
708 insert_json_option::<_, S::Error>(&mut map, "metadata", &self.metadata)?;
709 insert_json_option::<_, S::Error>(&mut map, "stop_sequences", &self.stop_sequences)?;
710 insert_json_option::<_, S::Error>(&mut map, "stream", &self.stream)?;
711 insert_json_option::<_, S::Error>(&mut map, "temperature", &self.temperature)?;
712 insert_json_option::<_, S::Error>(&mut map, "top_p", &self.top_p)?;
713 insert_json_option::<_, S::Error>(&mut map, "top_k", &self.top_k)?;
714
715 let anthropic_tools = self.tools.as_deref().unwrap_or_default();
716 let server_tools = self.server_tools.as_deref().unwrap_or_default();
717 if self.tools.is_some() || self.server_tools.is_some() {
718 let mut tools = Vec::with_capacity(anthropic_tools.len() + server_tools.len());
719 for tool in anthropic_tools {
720 tools.push(serde_json::to_value(tool).map_err(serde::ser::Error::custom)?);
721 }
722 for tool in server_tools {
723 tools.push(serde_json::to_value(tool).map_err(serde::ser::Error::custom)?);
724 }
725 map.insert("tools".to_string(), Value::Array(tools));
726 }
727
728 insert_json_option::<_, S::Error>(&mut map, "tool_choice", &self.tool_choice)?;
729 insert_json_option::<_, S::Error>(&mut map, "thinking", &self.thinking)?;
730 insert_json_option::<_, S::Error>(&mut map, "service_tier", &self.service_tier)?;
731 insert_json_option::<_, S::Error>(&mut map, "provider", &self.provider)?;
732 insert_json_option::<_, S::Error>(&mut map, "plugins", &self.plugins)?;
733 insert_json_option::<_, S::Error>(&mut map, "route", &self.route)?;
734 insert_json_option::<_, S::Error>(&mut map, "user", &self.user)?;
735 insert_json_option::<_, S::Error>(&mut map, "session_id", &self.session_id)?;
736 insert_json_option::<_, S::Error>(&mut map, "trace", &self.trace)?;
737 insert_json_option::<_, S::Error>(&mut map, "models", &self.models)?;
738 insert_json_option::<_, S::Error>(&mut map, "output_config", &self.output_config)?;
739
740 Value::Object(map).serialize(serializer)
741 }
742}
743
744impl AnthropicMessagesRequestBuilder {
745 strip_option_vec_setter!(stop_sequences, String);
746 strip_option_vec_setter!(tools, AnthropicTool);
747 strip_option_vec_setter!(server_tools, crate::types::ServerTool);
748 strip_option_vec_setter!(plugins, Plugin);
749 strip_option_vec_setter!(models, String);
750
751 pub fn tool(&mut self, tool: AnthropicTool) -> &mut Self {
752 if let Some(Some(ref mut existing_tools)) = self.tools {
753 existing_tools.push(tool);
754 } else {
755 self.tools = Some(Some(vec![tool]));
756 }
757 self
758 }
759
760 pub fn server_tool(&mut self, tool: crate::types::ServerTool) -> &mut Self {
761 if let Some(Some(ref mut existing_tools)) = self.server_tools {
762 existing_tools.push(tool);
763 } else {
764 self.server_tools = Some(Some(vec![tool]));
765 }
766 self
767 }
768
769 pub fn add_message(&mut self, message: AnthropicMessage) -> &mut Self {
770 if let Some(ref mut messages) = self.messages {
771 messages.push(message);
772 } else {
773 self.messages = Some(vec![message]);
774 }
775 self
776 }
777
778 pub fn thinking_enabled(&mut self, budget_tokens: u32) -> &mut Self {
779 self.thinking = Some(Some(AnthropicThinking::enabled(budget_tokens)));
780 self
781 }
782}
783
784impl AnthropicMessagesRequest {
785 pub fn builder() -> AnthropicMessagesRequestBuilder {
786 AnthropicMessagesRequestBuilder::default()
787 }
788
789 pub fn new(model: impl Into<String>, max_tokens: u32, messages: Vec<AnthropicMessage>) -> Self {
790 Self::builder()
791 .model(model.into())
792 .max_tokens(max_tokens)
793 .messages(messages)
794 .build()
795 .expect("Failed to build AnthropicMessagesRequest")
796 }
797
798 pub fn messages(&self) -> &[AnthropicMessage] {
799 &self.messages
800 }
801
802 pub fn tools(&self) -> Option<&[AnthropicTool]> {
803 self.tools.as_deref()
804 }
805
806 pub fn server_tools(&self) -> Option<&[crate::types::ServerTool]> {
807 self.server_tools.as_deref()
808 }
809
810 pub(crate) fn requires_openrouter_files_tool_header(&self) -> bool {
811 self.server_tools
812 .as_deref()
813 .is_some_and(|tools| tools.iter().any(crate::types::ServerTool::is_files_tool))
814 }
815
816 fn stream(&self, stream: bool) -> Self {
817 let mut req = self.clone();
818 req.stream = Some(stream);
819 req
820 }
821
822 pub fn experimental_metadata(&self) -> Option<OpenRouterExperimentalMetadata> {
823 self.experimental_metadata
824 }
825}
826
827#[derive(Serialize, Deserialize, Debug, Clone, Default)]
829#[non_exhaustive]
830pub struct AnthropicMessagesUsage {
831 #[serde(skip_serializing_if = "Option::is_none")]
832 pub input_tokens: Option<u64>,
833 #[serde(skip_serializing_if = "Option::is_none")]
834 pub output_tokens: Option<u64>,
835 #[serde(skip_serializing_if = "Option::is_none")]
836 pub cache_creation_input_tokens: Option<u64>,
837 #[serde(skip_serializing_if = "Option::is_none")]
838 pub cache_read_input_tokens: Option<u64>,
839 #[serde(skip_serializing_if = "Option::is_none")]
840 pub cache_creation: Option<AnthropicCacheCreation>,
841 #[serde(skip_serializing_if = "Option::is_none")]
842 pub service_tier: Option<String>,
843 #[serde(flatten)]
844 pub extra: HashMap<String, Value>,
845}
846
847#[derive(Serialize, Deserialize, Debug, Clone, Default)]
849#[non_exhaustive]
850pub struct AnthropicMessagesResponse {
851 #[serde(skip_serializing_if = "Option::is_none")]
852 pub id: Option<String>,
853 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
854 pub object_type: Option<String>,
855 #[serde(skip_serializing_if = "Option::is_none")]
856 pub role: Option<String>,
857 #[serde(default, skip_serializing_if = "Vec::is_empty")]
858 pub content: Vec<AnthropicContentPart>,
859 #[serde(skip_serializing_if = "Option::is_none")]
860 pub model: Option<String>,
861 #[serde(skip_serializing_if = "Option::is_none")]
862 pub stop_reason: Option<String>,
863 #[serde(skip_serializing_if = "Option::is_none")]
864 pub stop_sequence: Option<String>,
865 #[serde(skip_serializing_if = "Option::is_none")]
866 pub usage: Option<AnthropicMessagesUsage>,
867 #[serde(flatten)]
868 pub extra: HashMap<String, Value>,
869}
870
871#[derive(Serialize, Deserialize, Debug, Clone)]
873#[non_exhaustive]
874#[serde(tag = "type", rename_all = "snake_case")]
875pub enum AnthropicMessagesStreamEvent {
876 MessageStart {
877 message: Box<AnthropicMessagesResponse>,
878 },
879 MessageDelta {
880 delta: Value,
881 usage: Value,
882 },
883 MessageStop {
884 #[serde(default, skip_serializing_if = "Option::is_none")]
885 openrouter_metadata: Option<Value>,
886 #[serde(flatten)]
887 extra: HashMap<String, Value>,
888 },
889 ContentBlockStart {
890 index: u32,
891 content_block: Box<AnthropicContentPart>,
892 },
893 ContentBlockDelta {
894 index: u32,
895 delta: Value,
896 },
897 ContentBlockStop {
898 index: u32,
899 },
900 Ping,
901 Error {
902 error: Value,
903 },
904}
905
906impl AnthropicMessagesStreamEvent {
907 pub fn event_type(&self) -> &'static str {
908 match self {
909 Self::MessageStart { .. } => "message_start",
910 Self::MessageDelta { .. } => "message_delta",
911 Self::MessageStop { .. } => "message_stop",
912 Self::ContentBlockStart { .. } => "content_block_start",
913 Self::ContentBlockDelta { .. } => "content_block_delta",
914 Self::ContentBlockStop { .. } => "content_block_stop",
915 Self::Ping => "ping",
916 Self::Error { .. } => "error",
917 }
918 }
919}
920
921#[derive(Serialize, Deserialize, Debug, Clone)]
923#[non_exhaustive]
924pub struct AnthropicMessagesSseEvent {
925 pub event: String,
926 pub data: AnthropicMessagesStreamEvent,
927}
928
929pub async fn create_message(
931 base_url: &str,
932 api_key: &str,
933 x_title: &Option<String>,
934 http_referer: &Option<String>,
935 app_categories: &Option<Vec<String>>,
936 request: &AnthropicMessagesRequest,
937) -> Result<AnthropicMessagesResponse, OpenRouterError> {
938 let http_client = crate::transport::new_client()?;
939 create_message_with_client(
940 &http_client,
941 base_url,
942 api_key,
943 x_title,
944 http_referer,
945 app_categories,
946 request,
947 )
948 .await
949}
950
951pub(crate) async fn create_message_with_client(
952 http_client: &HttpClient,
953 base_url: &str,
954 api_key: &str,
955 x_title: &Option<String>,
956 http_referer: &Option<String>,
957 app_categories: &Option<Vec<String>>,
958 request: &AnthropicMessagesRequest,
959) -> Result<AnthropicMessagesResponse, OpenRouterError> {
960 let url = format!("{base_url}/messages");
961 let request = request.stream(false);
962
963 let request_builder = transport_request::with_experimental_metadata_header(
964 transport_request::with_client_request_headers(
965 transport_request::post(http_client, &url),
966 api_key,
967 x_title,
968 http_referer,
969 app_categories,
970 )?,
971 &request.experimental_metadata,
972 );
973 let request_builder = transport_request::with_openrouter_files_tool_header(
974 request_builder,
975 request.requires_openrouter_files_tool_header(),
976 );
977
978 let response = request_builder.json(&request).send().await?;
979
980 if response.status().is_success() {
981 let response_data: AnthropicMessagesResponse =
982 transport_response::parse_json_response(response, "messages API").await?;
983 Ok(response_data)
984 } else {
985 transport_response::handle_error(response).await?;
986 unreachable!()
987 }
988}
989
990pub async fn stream_messages(
992 base_url: &str,
993 api_key: &str,
994 x_title: &Option<String>,
995 http_referer: &Option<String>,
996 app_categories: &Option<Vec<String>>,
997 request: &AnthropicMessagesRequest,
998) -> Result<BoxStream<'static, Result<AnthropicMessagesSseEvent, OpenRouterError>>, OpenRouterError>
999{
1000 let http_client = crate::transport::new_client()?;
1001 stream_messages_with_client(
1002 &http_client,
1003 base_url,
1004 api_key,
1005 x_title,
1006 http_referer,
1007 app_categories,
1008 request,
1009 )
1010 .await
1011}
1012
1013pub(crate) async fn stream_messages_with_client(
1014 http_client: &HttpClient,
1015 base_url: &str,
1016 api_key: &str,
1017 x_title: &Option<String>,
1018 http_referer: &Option<String>,
1019 app_categories: &Option<Vec<String>>,
1020 request: &AnthropicMessagesRequest,
1021) -> Result<BoxStream<'static, Result<AnthropicMessagesSseEvent, OpenRouterError>>, OpenRouterError>
1022{
1023 let url = format!("{base_url}/messages");
1024 let request = request.stream(true);
1025
1026 let request_builder = transport_request::with_experimental_metadata_header(
1027 transport_request::with_client_request_headers(
1028 transport_request::post(http_client, &url),
1029 api_key,
1030 x_title,
1031 http_referer,
1032 app_categories,
1033 )?,
1034 &request.experimental_metadata,
1035 );
1036 let request_builder = transport_request::with_openrouter_files_tool_header(
1037 request_builder,
1038 request.requires_openrouter_files_tool_header(),
1039 );
1040
1041 let response = request_builder.json(&request).send().await?;
1042
1043 if response.status().is_success() {
1044 let stream = parse_sse_frames(response_lines(response))
1045 .filter_map(async |frame| match frame {
1046 Ok(frame) if frame.data == "[DONE]" => None,
1047 Ok(frame) => Some(
1048 serde_json::from_str::<AnthropicMessagesStreamEvent>(&frame.data)
1049 .map_err(OpenRouterError::Serialization)
1050 .map(|payload| AnthropicMessagesSseEvent {
1051 event: frame
1052 .event
1053 .unwrap_or_else(|| payload.event_type().to_string()),
1054 data: payload,
1055 }),
1056 ),
1057 Err(error) => Some(Err(error)),
1058 })
1059 .boxed();
1060
1061 Ok(stream)
1062 } else {
1063 transport_response::handle_error(response).await?;
1064 unreachable!()
1065 }
1066}