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 System,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224#[serde(untagged)]
225pub enum AnthropicMessageContent {
226 Text { content: String },
228 Blocks { content: Vec<AnthropicContentBlock> },
230}
231
232#[derive(Debug, Clone, Serialize)]
239#[serde(tag = "type")]
240pub enum AnthropicContentBlock {
241 #[serde(rename = "text")]
245 Text {
246 text: String,
247 #[serde(default, skip_serializing_if = "Option::is_none")]
248 citations: Option<Vec<serde_json::Value>>,
249 #[serde(default, skip_serializing_if = "Option::is_none")]
250 cache_control: Option<CacheControl>,
251 },
252 #[serde(rename = "image")]
254 Image { source: AnthropicImageSource },
255 #[serde(rename = "tool_use")]
257 ToolUse {
258 id: String,
259 name: String,
260 input: serde_json::Value,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
262 cache_control: Option<CacheControl>,
263 },
264 #[serde(rename = "tool_result")]
266 ToolResult {
267 tool_use_id: String,
268 #[serde(default, skip_serializing_if = "Option::is_none")]
269 content: Option<ToolResultContent>,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 is_error: Option<bool>,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
273 cache_control: Option<CacheControl>,
274 },
275 #[serde(rename = "thinking")]
277 Thinking {
278 thinking: String,
279 signature: String,
280 #[serde(default, skip_serializing_if = "Option::is_none")]
281 cache_control: Option<CacheControl>,
282 },
283 #[serde(rename = "redacted_thinking")]
287 RedactedThinking { data: String },
288 #[serde(rename = "server_tool_use")]
292 ServerToolUse {
293 id: String,
294 name: String,
295 #[serde(default)]
296 input: serde_json::Value,
297 },
298 #[serde(rename = "web_search_tool_result")]
301 WebSearchToolResult {
302 tool_use_id: String,
303 #[serde(default)]
304 content: serde_json::Value,
305 },
306 #[serde(untagged)]
310 Other(serde_json::Value),
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(untagged)]
317pub enum ToolResultContent {
318 Text(String),
319 Blocks(Vec<ToolResultContentBlock>),
320}
321
322impl ToolResultContent {
323 pub fn into_text(self) -> String {
325 match self {
326 ToolResultContent::Text(s) => s,
327 ToolResultContent::Blocks(blocks) => blocks
328 .into_iter()
329 .filter_map(|b| match b {
330 ToolResultContentBlock::Text { text } => Some(text),
331 ToolResultContentBlock::Other(_) => None,
332 })
333 .collect::<Vec<_>>()
334 .join(""),
335 }
336 }
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize)]
341#[serde(untagged)]
342pub enum ToolResultContentBlock {
343 Text {
344 text: String,
345 },
346 Other(serde_json::Value),
348}
349
350impl<'de> Deserialize<'de> for AnthropicContentBlock {
354 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
355 where
356 D: serde::Deserializer<'de>,
357 {
358 let value = serde_json::Value::deserialize(deserializer)?;
359 let block_type = value
360 .get("type")
361 .and_then(|t| t.as_str())
362 .unwrap_or("")
363 .to_string();
364
365 match block_type.as_str() {
366 "text" => {
367 let text = value
368 .get("text")
369 .and_then(|t| t.as_str())
370 .ok_or_else(|| serde::de::Error::missing_field("text"))?
371 .to_string();
372 let citations: Option<Vec<serde_json::Value>> = value
373 .get("citations")
374 .cloned()
375 .and_then(|v| serde_json::from_value(v).ok());
376 let cache_control: Option<CacheControl> = value
377 .get("cache_control")
378 .cloned()
379 .and_then(|v| serde_json::from_value(v).ok());
380 Ok(AnthropicContentBlock::Text {
381 text,
382 citations,
383 cache_control,
384 })
385 }
386 "image" => {
387 let source: AnthropicImageSource =
388 serde_json::from_value(value.get("source").cloned().unwrap_or_default())
389 .map_err(serde::de::Error::custom)?;
390 Ok(AnthropicContentBlock::Image { source })
391 }
392 "tool_use" => {
393 let id = value
394 .get("id")
395 .and_then(|v| v.as_str())
396 .ok_or_else(|| serde::de::Error::missing_field("id"))?
397 .to_string();
398 let name = value
399 .get("name")
400 .and_then(|v| v.as_str())
401 .ok_or_else(|| serde::de::Error::missing_field("name"))?
402 .to_string();
403 let input = value.get("input").cloned().unwrap_or(serde_json::json!({}));
404 let cache_control: Option<CacheControl> = value
405 .get("cache_control")
406 .cloned()
407 .and_then(|v| serde_json::from_value(v).ok());
408 Ok(AnthropicContentBlock::ToolUse {
409 id,
410 name,
411 input,
412 cache_control,
413 })
414 }
415 "tool_result" => {
416 let tool_use_id = value
417 .get("tool_use_id")
418 .and_then(|v| v.as_str())
419 .ok_or_else(|| serde::de::Error::missing_field("tool_use_id"))?
420 .to_string();
421 let content: Option<ToolResultContent> = value
422 .get("content")
423 .cloned()
424 .and_then(|v| serde_json::from_value(v).ok());
425 let is_error = value.get("is_error").and_then(|v| v.as_bool());
426 let cache_control: Option<CacheControl> = value
427 .get("cache_control")
428 .cloned()
429 .and_then(|v| serde_json::from_value(v).ok());
430 Ok(AnthropicContentBlock::ToolResult {
431 tool_use_id,
432 content,
433 is_error,
434 cache_control,
435 })
436 }
437 "thinking" => {
438 let thinking = value
439 .get("thinking")
440 .and_then(|v| v.as_str())
441 .ok_or_else(|| serde::de::Error::missing_field("thinking"))?
442 .to_string();
443 let signature = value
444 .get("signature")
445 .and_then(|v| v.as_str())
446 .ok_or_else(|| serde::de::Error::missing_field("signature"))?
447 .to_string();
448 let cache_control: Option<CacheControl> = value
449 .get("cache_control")
450 .cloned()
451 .and_then(|v| serde_json::from_value(v).ok());
452 Ok(AnthropicContentBlock::Thinking {
453 thinking,
454 signature,
455 cache_control,
456 })
457 }
458 "redacted_thinking" => {
459 let data = value
460 .get("data")
461 .and_then(|v| v.as_str())
462 .ok_or_else(|| serde::de::Error::missing_field("data"))?
463 .to_string();
464 Ok(AnthropicContentBlock::RedactedThinking { data })
465 }
466 "server_tool_use" => {
467 let id = value
468 .get("id")
469 .and_then(|v| v.as_str())
470 .ok_or_else(|| serde::de::Error::missing_field("id"))?
471 .to_string();
472 let name = value
473 .get("name")
474 .and_then(|v| v.as_str())
475 .ok_or_else(|| serde::de::Error::missing_field("name"))?
476 .to_string();
477 let input = value.get("input").cloned().unwrap_or(serde_json::json!({}));
478 Ok(AnthropicContentBlock::ServerToolUse { id, name, input })
479 }
480 "web_search_tool_result" => {
481 let tool_use_id = value
482 .get("tool_use_id")
483 .and_then(|v| v.as_str())
484 .ok_or_else(|| serde::de::Error::missing_field("tool_use_id"))?
485 .to_string();
486 let content = value
487 .get("content")
488 .cloned()
489 .unwrap_or(serde_json::json!([]));
490 Ok(AnthropicContentBlock::WebSearchToolResult {
491 tool_use_id,
492 content,
493 })
494 }
495 other => {
496 tracing::debug!(
497 "Unrecognized Anthropic content block type '{}', preserving as Other",
498 other
499 );
500 Ok(AnthropicContentBlock::Other(value))
501 }
502 }
503 }
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct AnthropicImageSource {
509 #[serde(rename = "type")]
510 pub source_type: String,
511 pub media_type: String,
512 pub data: String,
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct AnthropicTool {
524 pub name: String,
526 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
529 pub tool_type: Option<String>,
530 #[serde(skip_serializing_if = "Option::is_none")]
531 pub description: Option<String>,
532 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub input_schema: Option<serde_json::Value>,
536 #[serde(default, skip_serializing_if = "Option::is_none")]
538 pub cache_control: Option<CacheControl>,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize)]
543#[serde(untagged)]
544pub enum AnthropicToolChoice {
545 Named(AnthropicToolChoiceNamed),
548 Simple(AnthropicToolChoiceSimple),
550}
551
552#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct AnthropicToolChoiceSimple {
555 #[serde(rename = "type")]
556 pub choice_type: AnthropicToolChoiceMode,
557 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub disable_parallel_tool_use: Option<bool>,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
564#[serde(rename_all = "lowercase")]
565pub enum AnthropicToolChoiceMode {
566 Auto,
567 Any,
568 None,
569 Tool,
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize)]
574pub struct AnthropicToolChoiceNamed {
575 #[serde(rename = "type")]
576 pub choice_type: AnthropicToolChoiceMode,
577 pub name: String,
578 #[serde(default, skip_serializing_if = "Option::is_none")]
581 pub disable_parallel_tool_use: Option<bool>,
582}
583#[derive(Debug, Clone, Serialize, Deserialize)]
585pub struct AnthropicMessageResponse {
586 pub id: String,
587 #[serde(rename = "type")]
588 pub object_type: String,
589 pub role: String,
590 pub content: Vec<AnthropicResponseContentBlock>,
591 pub model: String,
592 pub stop_reason: Option<AnthropicStopReason>,
593 pub stop_sequence: Option<String>,
594 pub usage: AnthropicUsage,
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize)]
603#[serde(tag = "type")]
604pub enum AnthropicResponseContentBlock {
605 #[serde(rename = "thinking")]
606 Thinking { thinking: String, signature: String },
607 #[serde(rename = "text")]
608 Text {
609 text: String,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
611 citations: Option<Vec<serde_json::Value>>,
612 },
613 #[serde(rename = "tool_use")]
614 ToolUse {
615 id: String,
616 name: String,
617 input: serde_json::Value,
618 },
619 #[serde(rename = "redacted_thinking")]
620 RedactedThinking { data: String },
621 #[serde(rename = "server_tool_use")]
622 ServerToolUse {
623 id: String,
624 name: String,
625 #[serde(default)]
626 input: serde_json::Value,
627 },
628 #[serde(rename = "web_search_tool_result")]
629 WebSearchToolResult {
630 tool_use_id: String,
631 #[serde(default)]
632 content: serde_json::Value,
633 },
634 #[serde(untagged)]
638 Other(serde_json::Value),
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize, Default)]
643pub struct AnthropicUsage {
644 pub input_tokens: u32,
645 pub output_tokens: u32,
646 #[serde(default, skip_serializing_if = "Option::is_none")]
648 pub cache_creation_input_tokens: Option<u32>,
649 #[serde(default, skip_serializing_if = "Option::is_none")]
651 pub cache_read_input_tokens: Option<u32>,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
656#[serde(rename_all = "snake_case")]
657pub enum AnthropicStopReason {
658 EndTurn,
659 MaxTokens,
660 StopSequence,
661 ToolUse,
662 PauseTurn,
665 Refusal,
667}
668#[derive(Debug, Clone, Serialize, Deserialize)]
670#[serde(tag = "type")]
671pub enum AnthropicStreamEvent {
672 #[serde(rename = "message_start")]
673 MessageStart { message: AnthropicMessageResponse },
674
675 #[serde(rename = "content_block_start")]
676 ContentBlockStart {
677 index: u32,
678 content_block: AnthropicResponseContentBlock,
679 },
680
681 #[serde(rename = "content_block_delta")]
682 ContentBlockDelta { index: u32, delta: AnthropicDelta },
683
684 #[serde(rename = "content_block_stop")]
685 ContentBlockStop { index: u32 },
686
687 #[serde(rename = "message_delta")]
688 MessageDelta {
689 delta: AnthropicMessageDeltaBody,
690 usage: AnthropicUsage,
691 },
692
693 #[serde(rename = "message_stop")]
694 MessageStop {},
695
696 #[serde(rename = "ping")]
697 Ping {},
698
699 #[serde(rename = "error")]
700 Error { error: AnthropicErrorBody },
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize)]
705#[serde(tag = "type")]
706pub enum AnthropicDelta {
707 #[serde(rename = "thinking_delta")]
708 ThinkingDelta { thinking: String },
709 #[serde(rename = "text_delta")]
710 TextDelta { text: String },
711 #[serde(rename = "input_json_delta")]
712 InputJsonDelta { partial_json: String },
713 #[serde(rename = "signature_delta")]
715 SignatureDelta { signature: String },
716 #[serde(rename = "citations_delta")]
718 CitationsDelta { citation: serde_json::Value },
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize)]
723pub struct AnthropicMessageDeltaBody {
724 pub stop_reason: Option<AnthropicStopReason>,
725 #[serde(skip_serializing_if = "Option::is_none")]
726 pub stop_sequence: Option<String>,
727}
728#[derive(Debug, Clone, Serialize, Deserialize)]
730pub struct AnthropicErrorResponse {
731 #[serde(rename = "type")]
732 pub object_type: String,
733 pub error: AnthropicErrorBody,
734}
735
736#[derive(Debug, Clone, Serialize, Deserialize)]
738pub struct AnthropicErrorBody {
739 #[serde(rename = "type")]
740 pub error_type: String,
741 pub message: String,
742}
743
744impl AnthropicErrorResponse {
745 pub fn invalid_request(message: impl Into<String>) -> Self {
747 Self {
748 object_type: "error".to_string(),
749 error: AnthropicErrorBody {
750 error_type: "invalid_request_error".to_string(),
751 message: message.into(),
752 },
753 }
754 }
755
756 pub fn api_error(message: impl Into<String>) -> Self {
758 Self {
759 object_type: "error".to_string(),
760 error: AnthropicErrorBody {
761 error_type: "api_error".to_string(),
762 message: message.into(),
763 },
764 }
765 }
766
767 pub fn not_found(message: impl Into<String>) -> Self {
769 Self {
770 object_type: "error".to_string(),
771 error: AnthropicErrorBody {
772 error_type: "not_found_error".to_string(),
773 message: message.into(),
774 },
775 }
776 }
777}
778#[derive(Debug, Clone, Deserialize)]
780pub struct AnthropicCountTokensRequest {
781 pub model: String,
782 pub messages: Vec<AnthropicMessage>,
783 #[serde(
784 default,
785 skip_serializing_if = "Option::is_none",
786 deserialize_with = "deserialize_system_prompt"
787 )]
788 pub system: Option<SystemContent>,
789 #[serde(default)]
790 pub tools: Option<Vec<AnthropicTool>>,
791}
792
793#[derive(Debug, Clone, Serialize)]
795pub struct AnthropicCountTokensResponse {
796 pub input_tokens: u32,
797}
798
799impl AnthropicCountTokensRequest {
800 pub fn estimate_tokens(&self) -> u32 {
802 let mut total_len: usize = 0;
803
804 if let Some(system) = &self.system {
805 total_len += system.text.len();
806 }
807
808 for msg in &self.messages {
809 total_len += match msg.role {
811 AnthropicRole::User => 4,
812 AnthropicRole::Assistant => 9,
813 AnthropicRole::System => 6,
814 };
815 match &msg.content {
817 AnthropicMessageContent::Text { content } => total_len += content.len(),
818 AnthropicMessageContent::Blocks { content } => {
819 for block in content {
820 total_len += estimate_block_len(block);
821 }
822 }
823 }
824 }
825
826 if let Some(tools) = &self.tools {
827 for tool in tools {
828 total_len += tool.name.len();
829 if let Some(desc) = &tool.description {
830 total_len += desc.len();
831 }
832 if let Some(schema) = &tool.input_schema {
833 total_len += schema.to_string().len();
834 }
835 }
836 }
837
838 let tokens = total_len / 3;
839 if tokens == 0 && total_len > 0 {
840 1
841 } else {
842 tokens as u32
843 }
844 }
845}
846
847fn estimate_block_len(block: &AnthropicContentBlock) -> usize {
848 match block {
849 AnthropicContentBlock::Text { text, .. } => text.len(),
850 AnthropicContentBlock::ToolUse { name, input, .. } => name.len() + input.to_string().len(),
851 AnthropicContentBlock::ToolResult { content, .. } => content
852 .as_ref()
853 .map(|c| match c {
854 ToolResultContent::Text(s) => s.len(),
855 ToolResultContent::Blocks(blocks) => blocks
856 .iter()
857 .map(|b| match b {
858 ToolResultContentBlock::Text { text } => text.len(),
859 ToolResultContentBlock::Other(v) => v.to_string().len(),
860 })
861 .sum(),
862 })
863 .unwrap_or(0),
864 AnthropicContentBlock::Thinking { thinking, .. } => thinking.len(),
865 AnthropicContentBlock::RedactedThinking { data, .. } => data.len(),
866 AnthropicContentBlock::ServerToolUse { name, input, .. } => {
867 name.len() + input.to_string().len()
868 }
869 AnthropicContentBlock::WebSearchToolResult { content, .. } => content.to_string().len(),
870 AnthropicContentBlock::Image { .. } => 256, AnthropicContentBlock::Other(v) => v.to_string().len(),
872 }
873}