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