1use std::collections::{BTreeMap, BTreeSet};
14
15use serde_json::{Map, Value, json};
16
17use crate::completion::FinishReason;
18use crate::error::ProviderError;
19use crate::json_utils::Lenient;
20use crate::message::{CallId, DocumentRange, Source, SourceLocation, ToolName};
21use crate::observe::ObservedError;
22use crate::operation::{Block, CallFragment, Completion, Finish};
23use crate::providers::internal::set_fields;
24use crate::providers::internal::wire;
25use crate::wire::{
26 AdapterVerdict, Decoder, Flow, ObservationSink, Out, WireCitation, WireEvent, WireFrame,
27};
28
29const KNOWN_EVENT_TYPES: &[&str] = &[
32 "message",
33 "message_start",
34 "content_block_start",
35 "content_block_delta",
36 "content_block_stop",
37 "message_delta",
38 "message_stop",
39 "ping",
40 "error",
41];
42
43#[derive(Debug, Clone, PartialEq)]
47pub struct MessagesEvent {
48 pub fields: Value,
50 raw: Option<String>,
53}
54
55impl MessagesEvent {
56 fn kind(&self) -> &str {
57 self.fields.str("type").unwrap_or_default()
58 }
59
60 fn index(&self) -> Result<usize, ProviderError> {
62 self.fields
63 .u64("index")
64 .and_then(|index| usize::try_from(index).ok())
65 .ok_or_else(|| {
66 ProviderError::Response(format!("Anthropic `{}` names no block index", self.kind()))
67 })
68 }
69
70 fn item(&self, key: &str) -> Result<&Map<String, Value>, ProviderError> {
72 match self.fields.get(key) {
73 Some(Value::Object(item)) if item.get("type").is_some_and(Value::is_string) => Ok(item),
74 _ => Err(ProviderError::Response(format!(
75 "Anthropic `{}` carries no `{key}` with a string `type`",
76 self.kind()
77 ))),
78 }
79 }
80}
81
82#[derive(Debug, Clone, Default, PartialEq)]
84struct Counts {
85 input: Option<u64>,
86 output: Option<u64>,
87 cache_read: Option<u64>,
88 cache_creation: Option<u64>,
89 cache_creation_split: Option<Value>,
91 thinking: Option<u64>,
92}
93
94impl Counts {
95 fn of(usage: Option<&Value>) -> Self {
96 let count = |pointer: &str| usage?.at(pointer)?.as_u64_lenient();
97 Self {
98 input: count("/input_tokens"),
99 output: count("/output_tokens"),
100 cache_read: count("/cache_read_input_tokens"),
101 cache_creation: count("/cache_creation_input_tokens"),
102 cache_creation_split: usage
103 .and_then(|usage| usage.get("cache_creation"))
104 .filter(|split| split.is_object())
105 .cloned(),
106 thinking: count("/output_tokens_details/thinking_tokens"),
107 }
108 }
109
110 fn usage(&self) -> crate::completion::Usage {
114 let input = self.input.map(|uncached| {
115 uncached + self.cache_read.unwrap_or(0) + self.cache_creation.unwrap_or(0)
116 });
117 crate::completion::Usage {
118 input_tokens: input,
119 output_tokens: self.output,
120 cached_input_tokens: self.cache_read,
121 cache_creation_input_tokens: self.cache_creation,
122 reasoning_tokens: self.thinking,
123 total_tokens: input.zip(self.output).map(|(input, output)| input + output),
124 tool_use_prompt_tokens: None,
125 cost: None,
126 }
127 }
128}
129
130fn finish_of(reason: &str, details: Option<&Value>) -> (Option<FinishReason>, Option<String>) {
134 let reason = match reason {
135 "end_turn" | "stop_sequence" | "pause_turn" => FinishReason::Stop,
138 "max_tokens" | "model_context_window_exceeded" => FinishReason::Length,
139 "tool_use" => FinishReason::ToolCalls,
140 "refusal" => {
142 let explanation = details
143 .and_then(|details| details.str("explanation"))
144 .filter(|explanation| !explanation.is_empty())
145 .unwrap_or("The model refused to complete the request");
146 return (
147 Some(FinishReason::ContentFilter),
148 Some(explanation.to_owned()),
149 );
150 }
151 other => FinishReason::Other(other.to_owned()),
152 };
153 (Some(reason), None)
154}
155
156fn citation_of(citation: &Value) -> Option<WireCitation> {
162 let number = |key: &str| citation.u64(key).and_then(|n| u32::try_from(n).ok());
163 let range = |start: &str, end: &str| Some(number(start)?..number(end)?);
164 let location = match citation.str("type")? {
165 "char_location" => SourceLocation::Document {
166 index: number("document_index"),
167 id: citation.str("file_id").map(str::to_owned),
168 within: citation
169 .u64("start_char_index")
170 .zip(citation.u64("end_char_index"))
171 .map(|(start, end)| DocumentRange::Chars(start..end)),
172 },
173 "page_location" => SourceLocation::Document {
174 index: number("document_index"),
175 id: citation.str("file_id").map(str::to_owned),
176 within: range("start_page_number", "end_page_number").map(DocumentRange::Pages),
177 },
178 "content_block_location" => SourceLocation::Document {
179 index: number("document_index"),
180 id: citation.str("file_id").map(str::to_owned),
181 within: range("start_block_index", "end_block_index").map(DocumentRange::Blocks),
182 },
183 "search_result_location" => SourceLocation::SearchResult {
184 index: number("search_result_index")?,
185 source: citation.str("source")?.to_owned(),
186 blocks: range("start_block_index", "end_block_index"),
187 },
188 "web_search_result_location" => SourceLocation::Url {
189 url: citation.str("url")?.to_owned(),
190 },
191 _ => return None,
192 };
193 let mut source = Source::new(location);
194 if let Some(title) = citation
195 .str("document_title")
196 .or_else(|| citation.str("title"))
197 {
198 source = source.title(title);
199 }
200 if let Some(cited) = citation.str("cited_text") {
201 source = source.cited_text(cited);
202 }
203 Some(WireCitation::new(None, vec![source]))
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208enum Kind {
209 Text,
210 Thinking,
211 Redacted,
212 Call,
213 Opaque,
214}
215
216fn replayed_start(
219 previous: &[Map<String, Value>],
220 block: &Map<String, Value>,
221 reconnecting: bool,
222) -> bool {
223 reconnecting && previous.contains(block)
224}
225
226#[derive(Debug, Default)]
230pub struct MessagesDecoder {
231 open: BTreeMap<usize, (Kind, String)>,
233 started: BTreeMap<usize, Vec<Map<String, Value>>>,
235 reconnecting: bool,
236 replayed: BTreeSet<usize>,
238 message_started: bool,
240 opened: bool,
242 unsigned_thinking: bool,
244 start: Counts,
247 message_id: Option<String>,
248 response_model: Option<String>,
249 container: Option<Value>,
250}
251
252impl MessagesDecoder {
253 pub fn new(unsigned_thinking: bool) -> Self {
256 Self {
257 unsigned_thinking,
258 ..Self::default()
259 }
260 }
261
262 fn start(
264 &mut self,
265 index: usize,
266 block: Map<String, Value>,
267 out: &mut Out<'_, Completion>,
268 ) -> Result<(), ProviderError> {
269 if self.open.contains_key(&index) {
270 return Ok(());
271 }
272 if self
273 .started
274 .get(&index)
275 .is_some_and(|previous| replayed_start(previous, &block, self.reconnecting))
276 {
277 self.replayed.insert(index);
278 return Ok(());
279 }
280 self.replayed.remove(&index);
281 self.reconnecting = false;
282 self.started.entry(index).or_default().push(block.clone());
283 let block = Value::Object(block);
284 let kind = block.str("type").unwrap_or_default();
285 if kind == "fallback" {
288 if self.opened {
289 return Err(ProviderError::Response(
290 "Anthropic performed an unsupported mid-output model fallback".to_owned(),
291 ));
292 }
293 self.open.insert(index, (Kind::Opaque, String::new()));
294 return out.open(index, Block::Opaque { replay: false }, block);
295 }
296 self.opened = true;
297 let (opened, kind, text) = match kind {
298 "text" => (Block::Text, Kind::Text, block.str("text")),
299 "thinking" => (
300 Block::Reasoning { redacted: false },
301 Kind::Thinking,
302 block.str("thinking"),
303 ),
304 "redacted_thinking" => (Block::Reasoning { redacted: true }, Kind::Redacted, None),
305 "tool_use" => {
306 self.open.insert(index, (Kind::Call, String::new()));
307 let id = block.str("id").unwrap_or_default().to_owned();
308 let input = block.get("input").cloned().unwrap_or_default();
309 match ToolName::new(block.str("name").unwrap_or_default()) {
310 Ok(name) => {
311 let id = CallId::from_wire(&id);
312 out.open(index, Block::Call { id, name }, block)?;
313 }
314 Err(_) => out.fragment(
316 Some(index),
317 CallFragment {
318 id: Some(&id),
319 ..CallFragment::default()
320 },
321 )?,
322 }
323 return out.announce(index, input);
326 }
327 _ => (Block::Opaque { replay: true }, Kind::Opaque, None),
328 };
329 let text = text.unwrap_or_default().to_owned();
330 let citations: Vec<WireCitation> = match kind {
331 Kind::Text => block
332 .arr("citations")
333 .iter()
334 .filter_map(citation_of)
335 .collect(),
336 _ => Vec::new(),
337 };
338 self.open.insert(index, (kind, String::new()));
339 out.open(index, opened, block)?;
340 for citation in citations {
341 out.cite(index, citation);
342 }
343 out.push(index, &text)
344 }
345
346 fn delta(
351 &mut self,
352 index: usize,
353 delta: &Map<String, Value>,
354 out: &mut Out<'_, Completion>,
355 ) -> Result<(), ProviderError> {
356 if self.replayed.contains(&index) {
357 return Ok(());
358 }
359 let kind = delta
360 .get("type")
361 .and_then(Value::as_str)
362 .unwrap_or_default();
363 let fragment = |key: &str| {
365 delta.get(key).and_then(Value::as_str).ok_or_else(|| {
366 ProviderError::Response(format!("Anthropic `{kind}` carries no string `{key}`"))
367 })
368 };
369 if !self.started.contains_key(&index) {
372 let opened = match kind {
373 "text_delta" => Some(json!({"type": "text", "text": ""})),
374 "thinking_delta" => Some(json!({"type": "thinking", "thinking": ""})),
375 _ => None,
376 };
377 if let Some(Value::Object(block)) = opened {
378 self.start(index, block, out)?;
379 }
380 }
381 match kind {
382 "text_delta" => out.push(index, fragment("text")?)?,
383 "thinking_delta" => out.push(index, fragment("thinking")?)?,
384 "input_json_delta" => {
385 let fragment = fragment("partial_json")?;
386 let Some((kind @ (Kind::Call | Kind::Opaque), json)) = self.open.get_mut(&index)
387 else {
388 return Err(ProviderError::Response(format!(
389 "Anthropic streamed input to content block {index}, which takes none"
390 )));
391 };
392 json.push_str(fragment);
393 if *kind == Kind::Call {
394 out.push(index, fragment)?;
395 }
396 return Ok(());
397 }
398 "citations_delta" => {
399 let citation = delta.get("citation").cloned().unwrap_or_default();
400 let cited = citation_of(&citation);
401 out.edit(index, |item| {
402 if let Some(item) = item.as_object_mut() {
403 match item.get_mut("citations") {
404 Some(Value::Array(citations)) => citations.push(citation),
405 _ => {
406 item.insert("citations".to_owned(), Value::Array(vec![citation]));
407 }
408 }
409 }
410 })?;
411 if let Some(cited) = cited {
412 out.cite(index, cited);
413 }
414 return Ok(());
415 }
416 _ => {}
417 }
418 out.edit(index, |item| {
421 crate::operation::completion::merge(item, delta)
422 })
423 }
424
425 fn stop(&mut self, index: usize, out: &mut Out<'_, Completion>) -> Result<(), ProviderError> {
432 if self.replayed.remove(&index) {
433 return Ok(());
434 }
435 let Some((kind, json)) = self.open.remove(&index) else {
436 return out.finish(index);
437 };
438 let streamed = (!json.is_empty()).then(|| crate::json_utils::parse_tool_arguments(&json));
439 let unsigned = self.unsigned_thinking;
440 let mut complete = true;
441 out.edit(index, |item| {
442 let input = match streamed {
443 Some(Ok(parsed)) => Ok(Some(parsed)),
444 Some(Err(_)) => Err(()),
445 None => Ok(item.get("input").cloned()),
446 };
447 let kept = match (kind, input) {
448 (Kind::Text, _) => !item.str("text").unwrap_or_default().trim().is_empty(),
449 (Kind::Thinking, _) => {
450 unsigned || item.str("signature").is_some_and(|s| !s.is_empty())
451 }
452 (Kind::Redacted, _) => item.str("data").is_some_and(|data| !data.is_empty()),
453 (Kind::Opaque, Ok(None)) => true,
454 (Kind::Call | Kind::Opaque, Ok(Some(input @ Value::Object(_)))) => {
455 crate::operation::completion::merge(
456 item,
457 &set_fields([("input", Some(input))]),
458 );
459 true
460 }
461 (Kind::Call, Ok(None | Some(Value::Null))) => {
462 crate::operation::completion::merge(
463 item,
464 &set_fields([("input", Some(json!({})))]),
465 );
466 true
467 }
468 (Kind::Call, _) => false,
469 (Kind::Opaque, _) => {
470 complete = false;
471 true
472 }
473 };
474 if !kept {
475 *item = Value::Null;
476 }
477 })?;
478 if complete {
479 out.finish(index)
480 } else {
481 out.close(index)
482 }
483 }
484
485 fn metadata(&mut self, message: &Value) {
487 self.start = Counts::of(message.get("usage"));
488 self.message_id = message.str("id").map(str::to_owned);
489 self.response_model = message.str("model").map(str::to_owned);
490 self.note_container(message.get("container"));
491 }
492
493 fn note_container(&mut self, container: Option<&Value>) {
494 self.container = container
495 .filter(|c| !c.is_null())
496 .or(self.container.as_ref())
497 .cloned();
498 }
499
500 fn end(
505 &mut self,
506 usage: &Counts,
507 stop_reason: Option<&str>,
508 details: Option<&Value>,
509 mut out: Out<'_, Completion>,
510 ) -> Result<Flow, ProviderError> {
511 let open: Vec<usize> = self
512 .open
513 .iter()
514 .filter(|(_, (kind, json))| {
515 *kind != Kind::Call
516 || json.is_empty()
517 || crate::json_utils::parse_tool_arguments(json).is_ok_and(|v| v.is_object())
518 })
519 .map(|(index, _)| *index)
520 .collect();
521 for index in open {
522 self.stop(index, &mut out)?;
523 }
524 if let Some(container) = &self.container {
525 let index = out.fresh_index();
526 let item = json!({ "type": "container", "container": container });
527 out.whole(index, Block::Opaque { replay: true }, item, "")?;
528 }
529 let (reason, error) = stop_reason.map_or((None, None), |reason| finish_of(reason, details));
530 Ok(out.end(Finish {
531 usage: usage.usage(),
532 reason,
533 response_id: self.message_id.clone(),
534 model: self.response_model.clone(),
535 error,
536 }))
537 }
538
539 fn whole(
543 &mut self,
544 mut message: Value,
545 mut out: Out<'_, Completion>,
546 ) -> Result<Flow, ProviderError> {
547 self.metadata(&message);
548 let content = match message.get_mut("content").map(Value::take) {
549 Some(Value::Array(content)) => content,
550 _ => Vec::new(),
551 };
552 for (index, block) in content.into_iter().enumerate() {
553 let block = match block {
554 Value::Object(block) if block.get("type").is_some_and(Value::is_string) => block,
555 _ => {
556 return Err(ProviderError::Response(format!(
557 "Anthropic content block {index} has no string `type`"
558 )));
559 }
560 };
561 self.start(index, block, &mut out)?;
562 self.stop(index, &mut out)?;
563 }
564 let usage = self.start.clone();
565 self.end(
566 &usage,
567 message.str("stop_reason"),
568 message.get("stop_details"),
569 out,
570 )
571 }
572
573 fn terminal(&self, usage: Option<&Value>) -> Counts {
576 let (terminal, start) = (Counts::of(usage), self.start.clone());
577 Counts {
578 input: terminal.input.filter(|tokens| *tokens > 0).or(start.input),
581 cache_read: terminal.cache_read.or(start.cache_read),
582 cache_creation: terminal.cache_creation.or(start.cache_creation),
583 cache_creation_split: terminal.cache_creation_split.or(start.cache_creation_split),
584 ..terminal
585 }
586 }
587}
588
589impl<'id> Decoder<'id, Completion> for MessagesDecoder {
590 type Event = MessagesEvent;
591
592 fn classify(&self, frame: WireFrame) -> WireEvent<MessagesEvent> {
593 let data = frame.as_str();
594 wire::classify_tagged_frame::<Value>(&data, "type", |tag| KNOWN_EVENT_TYPES.contains(&tag))
595 .map(|fields| {
596 let raw = (fields.str("type") == Some("error")).then(|| data.to_string());
600 MessagesEvent { fields, raw }
601 })
602 }
603
604 fn decode(
605 &mut self,
606 event: MessagesEvent,
607 mut out: Out<'id, Completion>,
608 ) -> Result<Flow, ProviderError> {
609 match event.kind() {
610 "message" => return self.whole(event.fields, out),
611 "message_start" => {
614 if self.message_started && event.fields.get("message").is_some_and(Value::is_object)
615 {
616 self.reconnecting = true;
617 }
618 if !self.message_started
619 && let Some(message) = event.fields.get("message").filter(|m| m.is_object())
620 {
621 self.message_started = true;
622 self.metadata(message);
623 }
624 }
625 "content_block_start" => {
626 let block = event.item("content_block")?.clone();
627 self.start(event.index()?, block, &mut out)?;
628 }
629 "content_block_delta" => {
630 let delta = event.item("delta")?;
631 self.delta(event.index()?, delta, &mut out)?;
632 }
633 "content_block_stop" => self.stop(event.index()?, &mut out)?,
634 "message_delta" => {
635 let delta = event.fields.get("delta");
636 self.note_container(delta.and_then(|delta| delta.get("container")));
637 let Some(reason) = delta.and_then(|delta| delta.str("stop_reason")) else {
640 return Ok(Flow::More);
641 };
642 let usage = self.terminal(event.fields.get("usage"));
643 let details = delta.and_then(|delta| delta.get("stop_details"));
644 return self.end(&usage, Some(reason), details, out);
645 }
646 "error" => {
649 return Err(ProviderError::from_provider_body(
650 event.raw.unwrap_or_default(),
651 ));
652 }
653 _ => {}
656 }
657 Ok(Flow::More)
658 }
659}
660
661impl MessagesDecoder {
662 pub(crate) fn project(payload: &[u8], sink: &mut ObservationSink<'_>) {
667 let Ok(payload) = serde_json::from_slice::<Value>(payload) else {
668 return;
669 };
670 let fields = payload
671 .get("message")
672 .filter(|m| m.is_object())
673 .unwrap_or(&payload);
674 let stop_reason = fields
675 .str("stop_reason")
676 .or_else(|| payload.at("/delta/stop_reason").and_then(Value::as_str));
677 let verdict = AdapterVerdict {
678 finish_reason: stop_reason.map(|reason| sink.scrub(reason)),
679 block_reason: None,
680 detail: None,
681 model: fields.str("model").map(|model| sink.scrub(model)),
682 };
683 let response_id = fields.str("id").map(|id| sink.scrub(id));
684 sink.provider(verdict, response_id);
685 if let Some(error) = ObservedError::of(&payload) {
686 error.emit(sink);
687 }
688 }
689}
690
691pub(crate) mod document;
692
693pub use document::Message as MessagesDocument;
695
696#[cfg(test)]
697mod tests;