1use serde::{Deserialize, Serialize};
10
11#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
13pub struct CacheControl {
14 #[serde(rename = "type")]
15 pub control_type: CacheControlType,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub ttl: Option<String>,
19}
20
21#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
22#[serde(rename_all = "lowercase")]
23pub enum CacheControlType {
24 #[default]
25 Ephemeral,
26 #[serde(other)]
27 Unknown,
28}
29
30const MIN_TTL_SECONDS: u64 = 300;
31const MAX_TTL_SECONDS: u64 = 3600;
32
33impl CacheControl {
34 pub fn ttl_seconds(&self) -> u64 {
40 let raw = match self.ttl.as_deref() {
41 None => return MIN_TTL_SECONDS,
42 Some("5m") => 300,
43 Some("1h") => 3600,
44 Some(other) => match other.parse::<u64>() {
45 Ok(secs) => secs,
46 Err(_) => {
47 tracing::warn!("Unrecognized TTL '{}', defaulting to 300s", other);
48 return MIN_TTL_SECONDS;
49 }
50 },
51 };
52 raw.clamp(MIN_TTL_SECONDS, MAX_TTL_SECONDS)
53 }
54}
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct SystemContent {
58 pub text: String,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub cache_control: Option<CacheControl>,
63}
64
65fn deserialize_system_prompt<'de, D>(deserializer: D) -> Result<Option<SystemContent>, D::Error>
69where
70 D: serde::Deserializer<'de>,
71{
72 #[derive(Deserialize)]
73 #[serde(untagged)]
74 enum SystemPrompt {
75 Text(String),
76 Blocks(Vec<SystemBlock>),
77 }
78
79 #[derive(Deserialize)]
80 struct SystemBlock {
81 text: String,
82 #[serde(default)]
83 cache_control: Option<CacheControl>,
84 }
85
86 let maybe: Option<SystemPrompt> = Option::deserialize(deserializer)?;
87 Ok(maybe.map(|sp| match sp {
88 SystemPrompt::Text(s) => SystemContent {
89 text: s,
90 cache_control: None,
91 },
92 SystemPrompt::Blocks(blocks) => {
93 let cache_control = blocks.iter().rev().find_map(|b| b.cache_control.clone());
94 let text = blocks
95 .into_iter()
96 .map(|b| b.text)
97 .collect::<Vec<_>>()
98 .join("\n");
99 SystemContent {
100 text,
101 cache_control,
102 }
103 }
104 }))
105}
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct AnthropicCreateMessageRequest {
109 pub model: String,
111
112 pub max_tokens: u32,
114
115 pub messages: Vec<AnthropicMessage>,
117
118 #[serde(
120 default,
121 skip_serializing_if = "Option::is_none",
122 deserialize_with = "deserialize_system_prompt"
123 )]
124 pub system: Option<SystemContent>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub temperature: Option<f32>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub top_p: Option<f32>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub top_k: Option<u32>,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub stop_sequences: Option<Vec<String>>,
141
142 #[serde(default)]
144 pub stream: bool,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub metadata: Option<serde_json::Value>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub tools: Option<Vec<AnthropicTool>>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub tool_choice: Option<AnthropicToolChoice>,
157
158 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub cache_control: Option<CacheControl>,
164
165 #[serde(default, skip_serializing_if = "Option::is_none")]
170 pub thinking: Option<ThinkingConfig>,
171
172 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub service_tier: Option<String>,
175
176 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub container: Option<String>,
179
180 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub output_config: Option<serde_json::Value>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct ThinkingConfig {
195 #[serde(rename = "type")]
197 pub thinking_type: String,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub budget_tokens: Option<u32>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct AnthropicMessage {
206 pub role: AnthropicRole,
207 #[serde(flatten)]
208 pub content: AnthropicMessageContent,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
213#[serde(rename_all = "lowercase")]
214pub enum AnthropicRole {
215 User,
216 Assistant,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(untagged)]
222pub enum AnthropicMessageContent {
223 Text { content: String },
225 Blocks { content: Vec<AnthropicContentBlock> },
227}
228
229#[derive(Debug, Clone, Serialize)]
236#[serde(tag = "type")]
237pub enum AnthropicContentBlock {
238 #[serde(rename = "text")]
242 Text {
243 text: String,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
245 citations: Option<Vec<serde_json::Value>>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
247 cache_control: Option<CacheControl>,
248 },
249 #[serde(rename = "image")]
251 Image { source: AnthropicImageSource },
252 #[serde(rename = "tool_use")]
254 ToolUse {
255 id: String,
256 name: String,
257 input: serde_json::Value,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 cache_control: Option<CacheControl>,
260 },
261 #[serde(rename = "tool_result")]
263 ToolResult {
264 tool_use_id: String,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 content: Option<ToolResultContent>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 is_error: Option<bool>,
269 #[serde(default, skip_serializing_if = "Option::is_none")]
270 cache_control: Option<CacheControl>,
271 },
272 #[serde(rename = "thinking")]
274 Thinking {
275 thinking: String,
276 signature: String,
277 #[serde(default, skip_serializing_if = "Option::is_none")]
278 cache_control: Option<CacheControl>,
279 },
280 #[serde(rename = "redacted_thinking")]
284 RedactedThinking { data: String },
285 #[serde(rename = "server_tool_use")]
289 ServerToolUse {
290 id: String,
291 name: String,
292 #[serde(default)]
293 input: serde_json::Value,
294 },
295 #[serde(rename = "web_search_tool_result")]
298 WebSearchToolResult {
299 tool_use_id: String,
300 #[serde(default)]
301 content: serde_json::Value,
302 },
303 #[serde(untagged)]
307 Other(serde_json::Value),
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
313#[serde(untagged)]
314pub enum ToolResultContent {
315 Text(String),
316 Blocks(Vec<ToolResultContentBlock>),
317}
318
319impl ToolResultContent {
320 pub fn into_text(self) -> String {
322 match self {
323 ToolResultContent::Text(s) => s,
324 ToolResultContent::Blocks(blocks) => blocks
325 .into_iter()
326 .filter_map(|b| match b {
327 ToolResultContentBlock::Text { text } => Some(text),
328 ToolResultContentBlock::Other(_) => None,
329 })
330 .collect::<Vec<_>>()
331 .join(""),
332 }
333 }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
338#[serde(untagged)]
339pub enum ToolResultContentBlock {
340 Text {
341 text: String,
342 },
343 Other(serde_json::Value),
345}
346
347impl<'de> Deserialize<'de> for AnthropicContentBlock {
351 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
352 where
353 D: serde::Deserializer<'de>,
354 {
355 let value = serde_json::Value::deserialize(deserializer)?;
356 let block_type = value
357 .get("type")
358 .and_then(|t| t.as_str())
359 .unwrap_or("")
360 .to_string();
361
362 match block_type.as_str() {
363 "text" => {
364 let text = value
365 .get("text")
366 .and_then(|t| t.as_str())
367 .ok_or_else(|| serde::de::Error::missing_field("text"))?
368 .to_string();
369 let citations: Option<Vec<serde_json::Value>> = value
370 .get("citations")
371 .cloned()
372 .and_then(|v| serde_json::from_value(v).ok());
373 let cache_control: Option<CacheControl> = value
374 .get("cache_control")
375 .cloned()
376 .and_then(|v| serde_json::from_value(v).ok());
377 Ok(AnthropicContentBlock::Text {
378 text,
379 citations,
380 cache_control,
381 })
382 }
383 "image" => {
384 let source: AnthropicImageSource =
385 serde_json::from_value(value.get("source").cloned().unwrap_or_default())
386 .map_err(serde::de::Error::custom)?;
387 Ok(AnthropicContentBlock::Image { source })
388 }
389 "tool_use" => {
390 let id = value
391 .get("id")
392 .and_then(|v| v.as_str())
393 .ok_or_else(|| serde::de::Error::missing_field("id"))?
394 .to_string();
395 let name = value
396 .get("name")
397 .and_then(|v| v.as_str())
398 .ok_or_else(|| serde::de::Error::missing_field("name"))?
399 .to_string();
400 let input = value.get("input").cloned().unwrap_or(serde_json::json!({}));
401 let cache_control: Option<CacheControl> = value
402 .get("cache_control")
403 .cloned()
404 .and_then(|v| serde_json::from_value(v).ok());
405 Ok(AnthropicContentBlock::ToolUse {
406 id,
407 name,
408 input,
409 cache_control,
410 })
411 }
412 "tool_result" => {
413 let tool_use_id = value
414 .get("tool_use_id")
415 .and_then(|v| v.as_str())
416 .ok_or_else(|| serde::de::Error::missing_field("tool_use_id"))?
417 .to_string();
418 let content: Option<ToolResultContent> = value
419 .get("content")
420 .cloned()
421 .and_then(|v| serde_json::from_value(v).ok());
422 let is_error = value.get("is_error").and_then(|v| v.as_bool());
423 let cache_control: Option<CacheControl> = value
424 .get("cache_control")
425 .cloned()
426 .and_then(|v| serde_json::from_value(v).ok());
427 Ok(AnthropicContentBlock::ToolResult {
428 tool_use_id,
429 content,
430 is_error,
431 cache_control,
432 })
433 }
434 "thinking" => {
435 let thinking = value
436 .get("thinking")
437 .and_then(|v| v.as_str())
438 .ok_or_else(|| serde::de::Error::missing_field("thinking"))?
439 .to_string();
440 let signature = value
441 .get("signature")
442 .and_then(|v| v.as_str())
443 .ok_or_else(|| serde::de::Error::missing_field("signature"))?
444 .to_string();
445 let cache_control: Option<CacheControl> = value
446 .get("cache_control")
447 .cloned()
448 .and_then(|v| serde_json::from_value(v).ok());
449 Ok(AnthropicContentBlock::Thinking {
450 thinking,
451 signature,
452 cache_control,
453 })
454 }
455 "redacted_thinking" => {
456 let data = value
457 .get("data")
458 .and_then(|v| v.as_str())
459 .ok_or_else(|| serde::de::Error::missing_field("data"))?
460 .to_string();
461 Ok(AnthropicContentBlock::RedactedThinking { data })
462 }
463 "server_tool_use" => {
464 let id = value
465 .get("id")
466 .and_then(|v| v.as_str())
467 .ok_or_else(|| serde::de::Error::missing_field("id"))?
468 .to_string();
469 let name = value
470 .get("name")
471 .and_then(|v| v.as_str())
472 .ok_or_else(|| serde::de::Error::missing_field("name"))?
473 .to_string();
474 let input = value.get("input").cloned().unwrap_or(serde_json::json!({}));
475 Ok(AnthropicContentBlock::ServerToolUse { id, name, input })
476 }
477 "web_search_tool_result" => {
478 let tool_use_id = value
479 .get("tool_use_id")
480 .and_then(|v| v.as_str())
481 .ok_or_else(|| serde::de::Error::missing_field("tool_use_id"))?
482 .to_string();
483 let content = value
484 .get("content")
485 .cloned()
486 .unwrap_or(serde_json::json!([]));
487 Ok(AnthropicContentBlock::WebSearchToolResult {
488 tool_use_id,
489 content,
490 })
491 }
492 other => {
493 tracing::debug!(
494 "Unrecognized Anthropic content block type '{}', preserving as Other",
495 other
496 );
497 Ok(AnthropicContentBlock::Other(value))
498 }
499 }
500 }
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct AnthropicImageSource {
506 #[serde(rename = "type")]
507 pub source_type: String,
508 pub media_type: String,
509 pub data: String,
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct AnthropicTool {
521 pub name: String,
523 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
526 pub tool_type: Option<String>,
527 #[serde(skip_serializing_if = "Option::is_none")]
528 pub description: Option<String>,
529 #[serde(default, skip_serializing_if = "Option::is_none")]
532 pub input_schema: Option<serde_json::Value>,
533 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub cache_control: Option<CacheControl>,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize)]
540#[serde(untagged)]
541pub enum AnthropicToolChoice {
542 Named(AnthropicToolChoiceNamed),
545 Simple(AnthropicToolChoiceSimple),
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
551pub struct AnthropicToolChoiceSimple {
552 #[serde(rename = "type")]
553 pub choice_type: AnthropicToolChoiceMode,
554 #[serde(default, skip_serializing_if = "Option::is_none")]
557 pub disable_parallel_tool_use: Option<bool>,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
561#[serde(rename_all = "lowercase")]
562pub enum AnthropicToolChoiceMode {
563 Auto,
564 Any,
565 None,
566 Tool,
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize)]
571pub struct AnthropicToolChoiceNamed {
572 #[serde(rename = "type")]
573 pub choice_type: AnthropicToolChoiceMode,
574 pub name: String,
575 #[serde(default, skip_serializing_if = "Option::is_none")]
578 pub disable_parallel_tool_use: Option<bool>,
579}
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct AnthropicMessageResponse {
583 pub id: String,
584 #[serde(rename = "type")]
585 pub object_type: String,
586 pub role: String,
587 pub content: Vec<AnthropicResponseContentBlock>,
588 pub model: String,
589 pub stop_reason: Option<AnthropicStopReason>,
590 pub stop_sequence: Option<String>,
591 pub usage: AnthropicUsage,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
600#[serde(tag = "type")]
601pub enum AnthropicResponseContentBlock {
602 #[serde(rename = "thinking")]
603 Thinking { thinking: String, signature: String },
604 #[serde(rename = "text")]
605 Text {
606 text: String,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 citations: Option<Vec<serde_json::Value>>,
609 },
610 #[serde(rename = "tool_use")]
611 ToolUse {
612 id: String,
613 name: String,
614 input: serde_json::Value,
615 },
616 #[serde(rename = "redacted_thinking")]
617 RedactedThinking { data: String },
618 #[serde(rename = "server_tool_use")]
619 ServerToolUse {
620 id: String,
621 name: String,
622 #[serde(default)]
623 input: serde_json::Value,
624 },
625 #[serde(rename = "web_search_tool_result")]
626 WebSearchToolResult {
627 tool_use_id: String,
628 #[serde(default)]
629 content: serde_json::Value,
630 },
631 #[serde(untagged)]
635 Other(serde_json::Value),
636}
637
638#[derive(Debug, Clone, Serialize, Deserialize, Default)]
640pub struct AnthropicUsage {
641 pub input_tokens: u32,
642 pub output_tokens: u32,
643 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub cache_creation_input_tokens: Option<u32>,
646 #[serde(default, skip_serializing_if = "Option::is_none")]
648 pub cache_read_input_tokens: Option<u32>,
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
653#[serde(rename_all = "snake_case")]
654pub enum AnthropicStopReason {
655 EndTurn,
656 MaxTokens,
657 StopSequence,
658 ToolUse,
659 PauseTurn,
662 Refusal,
664}
665#[derive(Debug, Clone, Serialize, Deserialize)]
667#[serde(tag = "type")]
668pub enum AnthropicStreamEvent {
669 #[serde(rename = "message_start")]
670 MessageStart { message: AnthropicMessageResponse },
671
672 #[serde(rename = "content_block_start")]
673 ContentBlockStart {
674 index: u32,
675 content_block: AnthropicResponseContentBlock,
676 },
677
678 #[serde(rename = "content_block_delta")]
679 ContentBlockDelta { index: u32, delta: AnthropicDelta },
680
681 #[serde(rename = "content_block_stop")]
682 ContentBlockStop { index: u32 },
683
684 #[serde(rename = "message_delta")]
685 MessageDelta {
686 delta: AnthropicMessageDeltaBody,
687 usage: AnthropicUsage,
688 },
689
690 #[serde(rename = "message_stop")]
691 MessageStop {},
692
693 #[serde(rename = "ping")]
694 Ping {},
695
696 #[serde(rename = "error")]
697 Error { error: AnthropicErrorBody },
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize)]
702#[serde(tag = "type")]
703pub enum AnthropicDelta {
704 #[serde(rename = "thinking_delta")]
705 ThinkingDelta { thinking: String },
706 #[serde(rename = "text_delta")]
707 TextDelta { text: String },
708 #[serde(rename = "input_json_delta")]
709 InputJsonDelta { partial_json: String },
710 #[serde(rename = "signature_delta")]
712 SignatureDelta { signature: String },
713 #[serde(rename = "citations_delta")]
715 CitationsDelta { citation: serde_json::Value },
716}
717
718#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct AnthropicMessageDeltaBody {
721 pub stop_reason: Option<AnthropicStopReason>,
722 #[serde(skip_serializing_if = "Option::is_none")]
723 pub stop_sequence: Option<String>,
724}
725#[derive(Debug, Clone, Serialize, Deserialize)]
727pub struct AnthropicErrorResponse {
728 #[serde(rename = "type")]
729 pub object_type: String,
730 pub error: AnthropicErrorBody,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct AnthropicErrorBody {
736 #[serde(rename = "type")]
737 pub error_type: String,
738 pub message: String,
739}
740
741impl AnthropicErrorResponse {
742 pub fn invalid_request(message: impl Into<String>) -> Self {
744 Self {
745 object_type: "error".to_string(),
746 error: AnthropicErrorBody {
747 error_type: "invalid_request_error".to_string(),
748 message: message.into(),
749 },
750 }
751 }
752
753 pub fn api_error(message: impl Into<String>) -> Self {
755 Self {
756 object_type: "error".to_string(),
757 error: AnthropicErrorBody {
758 error_type: "api_error".to_string(),
759 message: message.into(),
760 },
761 }
762 }
763
764 pub fn not_found(message: impl Into<String>) -> Self {
766 Self {
767 object_type: "error".to_string(),
768 error: AnthropicErrorBody {
769 error_type: "not_found_error".to_string(),
770 message: message.into(),
771 },
772 }
773 }
774}
775#[derive(Debug, Clone, Deserialize)]
777pub struct AnthropicCountTokensRequest {
778 pub model: String,
779 pub messages: Vec<AnthropicMessage>,
780 #[serde(
781 default,
782 skip_serializing_if = "Option::is_none",
783 deserialize_with = "deserialize_system_prompt"
784 )]
785 pub system: Option<SystemContent>,
786 #[serde(default)]
787 pub tools: Option<Vec<AnthropicTool>>,
788}
789
790#[derive(Debug, Clone, Serialize)]
792pub struct AnthropicCountTokensResponse {
793 pub input_tokens: u32,
794}
795
796impl AnthropicCountTokensRequest {
797 pub fn estimate_tokens(&self) -> u32 {
799 let mut total_len: usize = 0;
800
801 if let Some(system) = &self.system {
802 total_len += system.text.len();
803 }
804
805 for msg in &self.messages {
806 total_len += match msg.role {
808 AnthropicRole::User => 4,
809 AnthropicRole::Assistant => 9,
810 };
811 match &msg.content {
813 AnthropicMessageContent::Text { content } => total_len += content.len(),
814 AnthropicMessageContent::Blocks { content } => {
815 for block in content {
816 total_len += estimate_block_len(block);
817 }
818 }
819 }
820 }
821
822 if let Some(tools) = &self.tools {
823 for tool in tools {
824 total_len += tool.name.len();
825 if let Some(desc) = &tool.description {
826 total_len += desc.len();
827 }
828 if let Some(schema) = &tool.input_schema {
829 total_len += schema.to_string().len();
830 }
831 }
832 }
833
834 let tokens = total_len / 3;
835 if tokens == 0 && total_len > 0 {
836 1
837 } else {
838 tokens as u32
839 }
840 }
841}
842
843fn estimate_block_len(block: &AnthropicContentBlock) -> usize {
844 match block {
845 AnthropicContentBlock::Text { text, .. } => text.len(),
846 AnthropicContentBlock::ToolUse { name, input, .. } => name.len() + input.to_string().len(),
847 AnthropicContentBlock::ToolResult { content, .. } => content
848 .as_ref()
849 .map(|c| match c {
850 ToolResultContent::Text(s) => s.len(),
851 ToolResultContent::Blocks(blocks) => blocks
852 .iter()
853 .map(|b| match b {
854 ToolResultContentBlock::Text { text } => text.len(),
855 ToolResultContentBlock::Other(v) => v.to_string().len(),
856 })
857 .sum(),
858 })
859 .unwrap_or(0),
860 AnthropicContentBlock::Thinking { thinking, .. } => thinking.len(),
861 AnthropicContentBlock::RedactedThinking { data, .. } => data.len(),
862 AnthropicContentBlock::ServerToolUse { name, input, .. } => {
863 name.len() + input.to_string().len()
864 }
865 AnthropicContentBlock::WebSearchToolResult { content, .. } => content.to_string().len(),
866 AnthropicContentBlock::Image { .. } => 256, AnthropicContentBlock::Other(v) => v.to_string().len(),
868 }
869}