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(default, skip_serializing_if = "Option::is_none")]
121 pub nvext: Option<serde_json::Value>,
122
123 #[serde(
125 default,
126 skip_serializing_if = "Option::is_none",
127 deserialize_with = "deserialize_system_prompt"
128 )]
129 pub system: Option<SystemContent>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub temperature: Option<f32>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub top_p: Option<f32>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub top_k: Option<u32>,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub stop_sequences: Option<Vec<String>>,
146
147 #[serde(default)]
149 pub stream: bool,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub metadata: Option<serde_json::Value>,
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub tools: Option<Vec<AnthropicTool>>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub tool_choice: Option<AnthropicToolChoice>,
162
163 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub cache_control: Option<CacheControl>,
169
170 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub thinking: Option<ThinkingConfig>,
176
177 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub service_tier: Option<String>,
180
181 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub container: Option<String>,
184
185 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub output_config: Option<serde_json::Value>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct ThinkingConfig {
200 #[serde(rename = "type")]
202 pub thinking_type: String,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub budget_tokens: Option<u32>,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct AnthropicMessage {
211 pub role: AnthropicRole,
212 #[serde(flatten)]
213 pub content: AnthropicMessageContent,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
218#[serde(rename_all = "lowercase")]
219pub enum AnthropicRole {
220 User,
221 Assistant,
222 System,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229#[serde(untagged)]
230pub enum AnthropicMessageContent {
231 Text { content: String },
233 Blocks { content: Vec<AnthropicContentBlock> },
235}
236
237#[derive(Debug, Clone, Serialize)]
244#[serde(tag = "type")]
245pub enum AnthropicContentBlock {
246 #[serde(rename = "text")]
250 Text {
251 text: String,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 citations: Option<Vec<serde_json::Value>>,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
255 cache_control: Option<CacheControl>,
256 },
257 #[serde(rename = "image")]
259 Image { source: AnthropicImageSource },
260 #[serde(rename = "tool_use")]
262 ToolUse {
263 id: String,
264 name: String,
265 input: serde_json::Value,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 cache_control: Option<CacheControl>,
268 },
269 #[serde(rename = "tool_result")]
271 ToolResult {
272 tool_use_id: String,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
274 content: Option<ToolResultContent>,
275 #[serde(skip_serializing_if = "Option::is_none")]
276 is_error: Option<bool>,
277 #[serde(default, skip_serializing_if = "Option::is_none")]
278 cache_control: Option<CacheControl>,
279 },
280 #[serde(rename = "thinking")]
282 Thinking {
283 thinking: String,
284 signature: String,
285 #[serde(default, skip_serializing_if = "Option::is_none")]
286 cache_control: Option<CacheControl>,
287 },
288 #[serde(rename = "redacted_thinking")]
292 RedactedThinking { data: String },
293 #[serde(rename = "server_tool_use")]
297 ServerToolUse {
298 id: String,
299 name: String,
300 #[serde(default)]
301 input: serde_json::Value,
302 },
303 #[serde(rename = "web_search_tool_result")]
306 WebSearchToolResult {
307 tool_use_id: String,
308 #[serde(default)]
309 content: serde_json::Value,
310 },
311 #[serde(untagged)]
315 Other(serde_json::Value),
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
321#[serde(untagged)]
322pub enum ToolResultContent {
323 Text(String),
324 Blocks(Vec<ToolResultContentBlock>),
325}
326
327impl ToolResultContent {
328 pub fn into_text(self) -> String {
330 match self {
331 ToolResultContent::Text(s) => s,
332 ToolResultContent::Blocks(blocks) => blocks
333 .into_iter()
334 .filter_map(|b| match b {
335 ToolResultContentBlock::Text { text } => Some(text),
336 ToolResultContentBlock::Other(_) => None,
337 })
338 .collect::<Vec<_>>()
339 .join(""),
340 }
341 }
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346#[serde(untagged)]
347pub enum ToolResultContentBlock {
348 Text {
349 text: String,
350 },
351 Other(serde_json::Value),
353}
354
355impl<'de> Deserialize<'de> for AnthropicContentBlock {
359 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
360 where
361 D: serde::Deserializer<'de>,
362 {
363 let value = serde_json::Value::deserialize(deserializer)?;
364 let block_type = value
365 .get("type")
366 .and_then(|t| t.as_str())
367 .unwrap_or("")
368 .to_string();
369
370 match block_type.as_str() {
371 "text" => {
372 let text = value
373 .get("text")
374 .and_then(|t| t.as_str())
375 .ok_or_else(|| serde::de::Error::missing_field("text"))?
376 .to_string();
377 let citations: Option<Vec<serde_json::Value>> = value
378 .get("citations")
379 .cloned()
380 .and_then(|v| serde_json::from_value(v).ok());
381 let cache_control: Option<CacheControl> = value
382 .get("cache_control")
383 .cloned()
384 .and_then(|v| serde_json::from_value(v).ok());
385 Ok(AnthropicContentBlock::Text {
386 text,
387 citations,
388 cache_control,
389 })
390 }
391 "image" => {
392 let source: AnthropicImageSource =
393 serde_json::from_value(value.get("source").cloned().unwrap_or_default())
394 .map_err(serde::de::Error::custom)?;
395 Ok(AnthropicContentBlock::Image { source })
396 }
397 "tool_use" => {
398 let id = value
399 .get("id")
400 .and_then(|v| v.as_str())
401 .ok_or_else(|| serde::de::Error::missing_field("id"))?
402 .to_string();
403 let name = value
404 .get("name")
405 .and_then(|v| v.as_str())
406 .ok_or_else(|| serde::de::Error::missing_field("name"))?
407 .to_string();
408 let input = value.get("input").cloned().unwrap_or(serde_json::json!({}));
409 let cache_control: Option<CacheControl> = value
410 .get("cache_control")
411 .cloned()
412 .and_then(|v| serde_json::from_value(v).ok());
413 Ok(AnthropicContentBlock::ToolUse {
414 id,
415 name,
416 input,
417 cache_control,
418 })
419 }
420 "tool_result" => {
421 let tool_use_id = value
422 .get("tool_use_id")
423 .and_then(|v| v.as_str())
424 .ok_or_else(|| serde::de::Error::missing_field("tool_use_id"))?
425 .to_string();
426 let content: Option<ToolResultContent> = value
427 .get("content")
428 .cloned()
429 .and_then(|v| serde_json::from_value(v).ok());
430 let is_error = value.get("is_error").and_then(|v| v.as_bool());
431 let cache_control: Option<CacheControl> = value
432 .get("cache_control")
433 .cloned()
434 .and_then(|v| serde_json::from_value(v).ok());
435 Ok(AnthropicContentBlock::ToolResult {
436 tool_use_id,
437 content,
438 is_error,
439 cache_control,
440 })
441 }
442 "thinking" => {
443 let thinking = value
444 .get("thinking")
445 .and_then(|v| v.as_str())
446 .ok_or_else(|| serde::de::Error::missing_field("thinking"))?
447 .to_string();
448 let signature = value
449 .get("signature")
450 .and_then(|v| v.as_str())
451 .ok_or_else(|| serde::de::Error::missing_field("signature"))?
452 .to_string();
453 let cache_control: Option<CacheControl> = value
454 .get("cache_control")
455 .cloned()
456 .and_then(|v| serde_json::from_value(v).ok());
457 Ok(AnthropicContentBlock::Thinking {
458 thinking,
459 signature,
460 cache_control,
461 })
462 }
463 "redacted_thinking" => {
464 let data = value
465 .get("data")
466 .and_then(|v| v.as_str())
467 .ok_or_else(|| serde::de::Error::missing_field("data"))?
468 .to_string();
469 Ok(AnthropicContentBlock::RedactedThinking { data })
470 }
471 "server_tool_use" => {
472 let id = value
473 .get("id")
474 .and_then(|v| v.as_str())
475 .ok_or_else(|| serde::de::Error::missing_field("id"))?
476 .to_string();
477 let name = value
478 .get("name")
479 .and_then(|v| v.as_str())
480 .ok_or_else(|| serde::de::Error::missing_field("name"))?
481 .to_string();
482 let input = value.get("input").cloned().unwrap_or(serde_json::json!({}));
483 Ok(AnthropicContentBlock::ServerToolUse { id, name, input })
484 }
485 "web_search_tool_result" => {
486 let tool_use_id = value
487 .get("tool_use_id")
488 .and_then(|v| v.as_str())
489 .ok_or_else(|| serde::de::Error::missing_field("tool_use_id"))?
490 .to_string();
491 let content = value
492 .get("content")
493 .cloned()
494 .unwrap_or(serde_json::json!([]));
495 Ok(AnthropicContentBlock::WebSearchToolResult {
496 tool_use_id,
497 content,
498 })
499 }
500 other => {
501 tracing::debug!(
502 "Unrecognized Anthropic content block type '{}', preserving as Other",
503 other
504 );
505 Ok(AnthropicContentBlock::Other(value))
506 }
507 }
508 }
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct AnthropicImageSource {
514 #[serde(rename = "type")]
515 pub source_type: String,
516 pub media_type: String,
517 pub data: String,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
528pub struct AnthropicTool {
529 pub name: String,
531 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
534 pub tool_type: Option<String>,
535 #[serde(skip_serializing_if = "Option::is_none")]
536 pub description: Option<String>,
537 #[serde(default, skip_serializing_if = "Option::is_none")]
540 pub input_schema: Option<serde_json::Value>,
541 #[serde(default, skip_serializing_if = "Option::is_none")]
543 pub cache_control: Option<CacheControl>,
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize)]
548#[serde(untagged)]
549pub enum AnthropicToolChoice {
550 Named(AnthropicToolChoiceNamed),
553 Simple(AnthropicToolChoiceSimple),
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct AnthropicToolChoiceSimple {
560 #[serde(rename = "type")]
561 pub choice_type: AnthropicToolChoiceMode,
562 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub disable_parallel_tool_use: Option<bool>,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
569#[serde(rename_all = "lowercase")]
570pub enum AnthropicToolChoiceMode {
571 Auto,
572 Any,
573 None,
574 Tool,
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct AnthropicToolChoiceNamed {
580 #[serde(rename = "type")]
581 pub choice_type: AnthropicToolChoiceMode,
582 pub name: String,
583 #[serde(default, skip_serializing_if = "Option::is_none")]
586 pub disable_parallel_tool_use: Option<bool>,
587}
588#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct AnthropicMessageResponse {
591 pub id: String,
592 #[serde(rename = "type")]
593 pub object_type: String,
594 pub role: String,
595 pub content: Vec<AnthropicResponseContentBlock>,
596 pub model: String,
597 pub stop_reason: Option<AnthropicStopReason>,
598 pub stop_sequence: Option<String>,
599 pub usage: AnthropicUsage,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize)]
608#[serde(tag = "type")]
609pub enum AnthropicResponseContentBlock {
610 #[serde(rename = "thinking")]
611 Thinking { thinking: String, signature: String },
612 #[serde(rename = "text")]
613 Text {
614 text: String,
615 #[serde(default, skip_serializing_if = "Option::is_none")]
616 citations: Option<Vec<serde_json::Value>>,
617 },
618 #[serde(rename = "tool_use")]
619 ToolUse {
620 id: String,
621 name: String,
622 input: serde_json::Value,
623 },
624 #[serde(rename = "redacted_thinking")]
625 RedactedThinking { data: String },
626 #[serde(rename = "server_tool_use")]
627 ServerToolUse {
628 id: String,
629 name: String,
630 #[serde(default)]
631 input: serde_json::Value,
632 },
633 #[serde(rename = "web_search_tool_result")]
634 WebSearchToolResult {
635 tool_use_id: String,
636 #[serde(default)]
637 content: serde_json::Value,
638 },
639 #[serde(untagged)]
643 Other(serde_json::Value),
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize, Default)]
648pub struct AnthropicUsage {
649 pub input_tokens: u32,
650 pub output_tokens: u32,
651 #[serde(default, skip_serializing_if = "Option::is_none")]
653 pub cache_creation_input_tokens: Option<u32>,
654 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub cache_read_input_tokens: Option<u32>,
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
661#[serde(rename_all = "snake_case")]
662pub enum AnthropicStopReason {
663 EndTurn,
664 MaxTokens,
665 StopSequence,
666 ToolUse,
667 PauseTurn,
670 Refusal,
672}
673#[derive(Debug, Clone, Serialize, Deserialize)]
675#[serde(tag = "type")]
676pub enum AnthropicStreamEvent {
677 #[serde(rename = "message_start")]
678 MessageStart { message: AnthropicMessageResponse },
679
680 #[serde(rename = "content_block_start")]
681 ContentBlockStart {
682 index: u32,
683 content_block: AnthropicResponseContentBlock,
684 },
685
686 #[serde(rename = "content_block_delta")]
687 ContentBlockDelta { index: u32, delta: AnthropicDelta },
688
689 #[serde(rename = "content_block_stop")]
690 ContentBlockStop { index: u32 },
691
692 #[serde(rename = "message_delta")]
693 MessageDelta {
694 delta: AnthropicMessageDeltaBody,
695 usage: AnthropicUsage,
696 },
697
698 #[serde(rename = "message_stop")]
699 MessageStop {},
700
701 #[serde(rename = "ping")]
702 Ping {},
703
704 #[serde(rename = "error")]
705 Error { error: AnthropicErrorBody },
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize)]
710#[serde(tag = "type")]
711pub enum AnthropicDelta {
712 #[serde(rename = "thinking_delta")]
713 ThinkingDelta { thinking: String },
714 #[serde(rename = "text_delta")]
715 TextDelta { text: String },
716 #[serde(rename = "input_json_delta")]
717 InputJsonDelta { partial_json: String },
718 #[serde(rename = "signature_delta")]
720 SignatureDelta { signature: String },
721 #[serde(rename = "citations_delta")]
723 CitationsDelta { citation: serde_json::Value },
724}
725
726#[derive(Debug, Clone, Serialize, Deserialize)]
728pub struct AnthropicMessageDeltaBody {
729 pub stop_reason: Option<AnthropicStopReason>,
730 #[serde(skip_serializing_if = "Option::is_none")]
731 pub stop_sequence: Option<String>,
732}
733#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct AnthropicErrorResponse {
736 #[serde(rename = "type")]
737 pub object_type: String,
738 pub error: AnthropicErrorBody,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize)]
743pub struct AnthropicErrorBody {
744 #[serde(rename = "type")]
745 pub error_type: String,
746 pub message: String,
747}
748
749impl AnthropicErrorResponse {
750 pub fn invalid_request(message: impl Into<String>) -> Self {
752 Self {
753 object_type: "error".to_string(),
754 error: AnthropicErrorBody {
755 error_type: "invalid_request_error".to_string(),
756 message: message.into(),
757 },
758 }
759 }
760
761 pub fn api_error(message: impl Into<String>) -> Self {
763 Self {
764 object_type: "error".to_string(),
765 error: AnthropicErrorBody {
766 error_type: "api_error".to_string(),
767 message: message.into(),
768 },
769 }
770 }
771
772 pub fn not_found(message: impl Into<String>) -> Self {
774 Self {
775 object_type: "error".to_string(),
776 error: AnthropicErrorBody {
777 error_type: "not_found_error".to_string(),
778 message: message.into(),
779 },
780 }
781 }
782}
783#[derive(Debug, Clone, Deserialize)]
785pub struct AnthropicCountTokensRequest {
786 pub model: String,
787 pub messages: Vec<AnthropicMessage>,
788 #[serde(
789 default,
790 skip_serializing_if = "Option::is_none",
791 deserialize_with = "deserialize_system_prompt"
792 )]
793 pub system: Option<SystemContent>,
794 #[serde(default)]
795 pub tools: Option<Vec<AnthropicTool>>,
796}
797
798#[derive(Debug, Clone, Serialize)]
800pub struct AnthropicCountTokensResponse {
801 pub input_tokens: u32,
802}
803
804impl AnthropicCountTokensRequest {
805 pub fn estimate_tokens(&self) -> u32 {
807 let mut total_len: usize = 0;
808
809 if let Some(system) = &self.system {
810 total_len += system.text.len();
811 }
812
813 for msg in &self.messages {
814 total_len += match msg.role {
816 AnthropicRole::User => 4,
817 AnthropicRole::Assistant => 9,
818 AnthropicRole::System => 6,
819 };
820 match &msg.content {
822 AnthropicMessageContent::Text { content } => total_len += content.len(),
823 AnthropicMessageContent::Blocks { content } => {
824 for block in content {
825 total_len += estimate_block_len(block);
826 }
827 }
828 }
829 }
830
831 if let Some(tools) = &self.tools {
832 for tool in tools {
833 total_len += tool.name.len();
834 if let Some(desc) = &tool.description {
835 total_len += desc.len();
836 }
837 if let Some(schema) = &tool.input_schema {
838 total_len += schema.to_string().len();
839 }
840 }
841 }
842
843 let tokens = total_len / 3;
844 if tokens == 0 && total_len > 0 {
845 1
846 } else {
847 tokens as u32
848 }
849 }
850}
851
852fn estimate_block_len(block: &AnthropicContentBlock) -> usize {
853 match block {
854 AnthropicContentBlock::Text { text, .. } => text.len(),
855 AnthropicContentBlock::ToolUse { name, input, .. } => name.len() + input.to_string().len(),
856 AnthropicContentBlock::ToolResult { content, .. } => content
857 .as_ref()
858 .map(|c| match c {
859 ToolResultContent::Text(s) => s.len(),
860 ToolResultContent::Blocks(blocks) => blocks
861 .iter()
862 .map(|b| match b {
863 ToolResultContentBlock::Text { text } => text.len(),
864 ToolResultContentBlock::Other(v) => v.to_string().len(),
865 })
866 .sum(),
867 })
868 .unwrap_or(0),
869 AnthropicContentBlock::Thinking { thinking, .. } => thinking.len(),
870 AnthropicContentBlock::RedactedThinking { data, .. } => data.len(),
871 AnthropicContentBlock::ServerToolUse { name, input, .. } => {
872 name.len() + input.to_string().len()
873 }
874 AnthropicContentBlock::WebSearchToolResult { content, .. } => content.to_string().len(),
875 AnthropicContentBlock::Image { .. } => 256, AnthropicContentBlock::Other(v) => v.to_string().len(),
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883
884 #[test]
885 fn messages_request_keeps_nvext_opaque() {
886 let request: AnthropicCreateMessageRequest = serde_json::from_value(serde_json::json!({
887 "model": "test-model",
888 "max_tokens": 16,
889 "messages": [{"role": "user", "content": "hi"}],
890 "nvext": {
891 "unknown_future_extension": {"nested": true},
892 "agent_context": {"trajectory_id": 7}
893 }
894 }))
895 .unwrap();
896
897 let nvext = request.nvext.expect("opaque nvext value");
898 assert_eq!(nvext["unknown_future_extension"]["nested"], true);
899 assert_eq!(nvext["agent_context"]["trajectory_id"], 7);
900 }
901}