1use serde::{Deserialize, Serialize};
2use serde_json::{Value, json};
3
4use super::completion::{CompletionResponse, Content, anthropic_usage_totals, map_finish_reason};
5use crate::error::ProviderError;
6use crate::message::ReasoningContent;
7use crate::observe::ObservedError;
8use crate::operation::{
9 CallFragment, Completion, Finish, IfMalformed, ReasoningPart, Seal, TextPart,
10};
11use crate::providers::internal::wire;
12use crate::wire::{
13 AdapterEvent, AdapterUsage, AdapterVerdict, Decoder, Flow, ObservationSink, Out, WireEvent,
14 WireFrame,
15};
16use std::collections::HashMap;
17
18const KNOWN_EVENT_TYPES: &[&str] = &[
22 "message",
24 "message_start",
25 "content_block_start",
26 "content_block_delta",
27 "content_block_stop",
28 "message_delta",
29 "message_stop",
30 "ping",
31 "error",
32];
33
34#[derive(Debug, Deserialize)]
35#[serde(tag = "type", rename_all = "snake_case")]
36pub enum StreamingEvent {
37 MessageStart {
38 #[serde(default)]
40 message: Option<CompletionResponse>,
41 },
42 Message {
46 #[serde(flatten)]
47 message: CompletionResponse,
48 },
49 ContentBlockStart {
50 index: usize,
51 content_block: Content,
52 },
53 ContentBlockDelta {
54 index: usize,
55 delta: ContentDelta,
56 },
57 ContentBlockStop {
58 index: usize,
59 },
60 MessageDelta {
61 delta: MessageDelta,
62 usage: PartialUsage,
63 },
64 MessageStop,
65 Ping,
67 Error {
69 #[allow(dead_code)]
71 error: serde_json::Value,
72 #[serde(skip)]
75 raw: String,
76 },
77}
78
79#[derive(Debug)]
80pub enum ContentDelta {
81 TextDelta {
82 text: String,
83 },
84 InputJsonDelta {
85 partial_json: String,
86 },
87 ThinkingDelta {
88 thinking: String,
89 },
90 SignatureDelta {
91 signature: String,
92 },
93 CitationsDelta {
94 citation: super::completion::Citation,
95 },
96 Unknown(serde_json::Value),
98}
99
100impl<'de> Deserialize<'de> for ContentDelta {
103 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104 where
105 D: serde::Deserializer<'de>,
106 {
107 let value = serde_json::Value::deserialize(deserializer)?;
108 if !value.is_object() {
110 return Err(serde::de::Error::custom("content delta must be an object"));
111 }
112 let str_field = |tag: &str, field: &str| -> Result<String, D::Error> {
113 value
114 .get(field)
115 .and_then(serde_json::Value::as_str)
116 .map(ToOwned::to_owned)
117 .ok_or_else(|| {
118 serde::de::Error::custom(format!(
119 "`{tag}` content delta is missing a string `{field}` field"
120 ))
121 })
122 };
123 match value.get("type").cloned() {
124 Some(serde_json::Value::String(tag)) => match tag.as_str() {
125 "text_delta" => Ok(Self::TextDelta {
126 text: str_field("text_delta", "text")?,
127 }),
128 "input_json_delta" => Ok(Self::InputJsonDelta {
129 partial_json: str_field("input_json_delta", "partial_json")?,
130 }),
131 "thinking_delta" => Ok(Self::ThinkingDelta {
132 thinking: str_field("thinking_delta", "thinking")?,
133 }),
134 "signature_delta" => Ok(Self::SignatureDelta {
135 signature: str_field("signature_delta", "signature")?,
136 }),
137 "citations_delta" => {
138 let citation = value.get("citation").cloned().ok_or_else(|| {
139 serde::de::Error::custom(
140 "`citations_delta` content delta is missing a `citation` field",
141 )
142 })?;
143 Ok(Self::CitationsDelta {
144 citation: serde_json::from_value(citation)
145 .map_err(serde::de::Error::custom)?,
146 })
147 }
148 _ => Ok(Self::Unknown(value)),
149 },
150 Some(_) => Err(serde::de::Error::custom(
151 "content delta `type` must be a string",
152 )),
153 None => Err(serde::de::Error::custom(
155 "content delta is missing a `type` field",
156 )),
157 }
158 }
159}
160
161#[derive(Debug, Deserialize)]
162pub struct MessageDelta {
163 pub stop_reason: Option<String>,
164 pub stop_sequence: Option<String>,
165}
166
167#[derive(Debug, Deserialize, Clone, Serialize, Default)]
168pub struct PartialUsage {
169 pub output_tokens: usize,
170 #[serde(default)]
171 pub input_tokens: Option<usize>,
172 #[serde(default)]
173 pub cache_creation_input_tokens: Option<u64>,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub cache_creation: Option<super::completion::CacheCreation>,
179 #[serde(default)]
180 pub cache_read_input_tokens: Option<u64>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub output_tokens_details: Option<super::completion::OutputTokensDetails>,
184}
185
186impl From<&PartialUsage> for crate::completion::Usage {
187 fn from(value: &PartialUsage) -> crate::completion::Usage {
188 anthropic_usage_totals(
189 value.input_tokens.map(|tokens| tokens as u64),
190 value.output_tokens as u64,
191 value.cache_read_input_tokens,
192 value.cache_creation_input_tokens,
193 value.output_tokens_details,
194 )
195 }
196}
197
198impl From<PartialUsage> for crate::completion::Usage {
199 fn from(value: PartialUsage) -> crate::completion::Usage {
200 (&value).into()
201 }
202}
203
204struct ServerToolUseState {
207 name: String,
208 id: String,
209 initial_input: Value,
210 input_json: String,
211}
212
213pub struct MessagesDecoder<'id> {
216 texts: HashMap<usize, TextPart<'id>>,
218 thinking: HashMap<usize, (ReasoningPart<'id>, String, String)>,
221 current_tool_call: Option<usize>,
224 server_tool_uses: HashMap<usize, ServerToolUseState>,
225 input_tokens: u64,
226 cache_creation: Option<super::completion::CacheCreation>,
229 cache_read_input_tokens: Option<u64>,
232 cache_creation_input_tokens: Option<u64>,
233 message_id: Option<String>,
234 response_model: Option<String>,
235}
236
237impl Default for MessagesDecoder<'_> {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243impl MessagesDecoder<'_> {
244 pub fn new() -> Self {
246 Self {
247 texts: HashMap::new(),
248 thinking: HashMap::new(),
249 current_tool_call: None,
250 server_tool_uses: HashMap::new(),
251 input_tokens: 0,
252 cache_creation: None,
253 cache_read_input_tokens: None,
254 cache_creation_input_tokens: None,
255 message_id: None,
256 response_model: None,
257 }
258 }
259}
260
261impl<'id> MessagesDecoder<'id> {
262 fn interpret_content(
264 &mut self,
265 event: StreamingEvent,
266 out: &mut Out<'id, Completion>,
267 ) -> Result<(), ProviderError> {
268 match event {
269 StreamingEvent::ContentBlockDelta { index, delta } => match delta {
270 ContentDelta::TextDelta { text } => {
271 if self.current_tool_call.is_none() {
272 let part = self.texts.entry(index).or_insert_with(|| out.text());
273 out.push_text(part, &text);
274 }
275 }
276 ContentDelta::InputJsonDelta { partial_json } => {
277 if let Some(server_tool_use) = self.server_tool_uses.get_mut(&index) {
278 server_tool_use.input_json.push_str(&partial_json);
279 return Ok(());
280 }
281 if let Some(call) = self.current_tool_call {
282 out.call_fragment(
283 call,
284 CallFragment {
285 arguments: Some(partial_json.as_str()),
286 ..CallFragment::default()
287 },
288 )?;
289 }
290 }
291 ContentDelta::ThinkingDelta { thinking } => {
292 let (part, _, _) = self
293 .thinking
294 .entry(index)
295 .or_insert_with(|| (out.reasoning(), String::new(), String::new()));
296 out.push_reasoning(part, &thinking);
297 }
298 ContentDelta::SignatureDelta { signature } => {
299 let (_, fragments, _) = self
300 .thinking
301 .entry(index)
302 .or_insert_with(|| (out.reasoning(), String::new(), String::new()));
303 fragments.push_str(&signature);
305 }
306 ContentDelta::CitationsDelta { citation } => {
307 if let Some(params) = crate::message::AdditionalParams::from_entries([(
308 "citations",
309 json!([citation]),
310 )]) {
311 let part = self.texts.entry(index).or_insert_with(|| out.text());
312 out.text_params(part, params);
313 }
314 }
315 ContentDelta::Unknown(value) => {
316 tracing::warn!(
318 delta_type = value.get("type").and_then(serde_json::Value::as_str),
319 "skipping unrecognized Anthropic content delta type"
320 );
321 }
322 },
323 StreamingEvent::ContentBlockStart {
324 index,
325 content_block,
326 } => match content_block {
327 Content::Text {
329 text: _,
330 citations,
331 cache_control: _,
332 } => {
333 let part = out.text();
334 if let Some(params) = crate::message::AdditionalParams::from_entries(
335 (!citations.is_empty()).then(|| ("citations", json!(citations))),
336 ) {
337 out.text_params(&part, params);
338 }
339 self.texts.insert(index, part);
340 }
341 Content::ServerToolUse { id, name, input } => {
342 self.server_tool_uses.insert(
343 index,
344 ServerToolUseState {
345 name,
346 id,
347 initial_input: input,
348 input_json: String::new(),
349 },
350 );
351 }
352 raw @ (Content::WebSearchToolResult { .. }
353 | Content::CodeExecutionToolResult { .. }) => {
354 if let Some(params) = crate::message::AdditionalParams::from_entries([(
355 super::completion::ANTHROPIC_RAW_CONTENT_KEY,
356 json!(raw),
357 )]) {
358 let part = out.text();
359 out.text_params(&part, params);
360 self.texts.insert(index, part);
361 }
362 }
363 Content::ToolUse { id, name, .. } => {
364 self.current_tool_call = Some(index);
365 out.call_fragment(
366 index,
367 CallFragment {
368 id: Some(id.as_str()),
369 name: Some(name.as_str()),
370 ..CallFragment::default()
371 },
372 )?;
373 }
374 Content::Thinking {
375 thinking,
376 signature,
377 } => {
378 let part = out.reasoning();
381 out.push_reasoning(&part, &thinking);
382 self.thinking
383 .insert(index, (part, String::new(), signature.unwrap_or_default()));
384 }
385 Content::RedactedThinking { data } => {
386 out.reasoning_block(crate::message::Reasoning {
387 id: None,
388 content: vec![ReasoningContent::Redacted { data }],
389 });
390 }
391 Content::Image { .. } | Content::ToolResult { .. } | Content::Document { .. } => {}
394 },
395 StreamingEvent::ContentBlockStop { index } => {
396 if let Some((part, fragments, initial)) = self.thinking.remove(&index) {
399 let signature = if fragments.is_empty() {
400 initial
401 } else {
402 fragments
403 };
404 out.close_reasoning(
405 part,
406 Seal {
407 signature: (!signature.is_empty()).then_some(signature),
408 ..Seal::default()
409 },
410 );
411 return Ok(());
412 }
413
414 if let Some(server_tool_use) = self.server_tool_uses.remove(&index) {
415 let input = if server_tool_use.input_json.is_empty() {
416 if server_tool_use.initial_input.is_null() {
417 json!({})
418 } else {
419 server_tool_use.initial_input
420 }
421 } else {
422 serde_json::from_str(&server_tool_use.input_json)?
423 };
424 if let Some(params) = crate::message::AdditionalParams::from_entries([(
425 super::completion::ANTHROPIC_RAW_CONTENT_KEY,
426 json!(Content::ServerToolUse {
427 id: server_tool_use.id,
428 name: server_tool_use.name,
429 input,
430 }),
431 )]) {
432 let part = out.text();
433 out.text_params(&part, params);
434 out.close_text(part);
435 }
436 return Ok(());
437 }
438
439 if self.current_tool_call == Some(index) {
442 self.current_tool_call = None;
443 out.close_pending(index, IfMalformed::Fail)?;
444 return Ok(());
445 }
446
447 if let Some(part) = self.texts.remove(&index) {
448 out.close_text(part);
449 }
450 }
451 StreamingEvent::Message { .. }
452 | StreamingEvent::MessageStart { .. }
453 | StreamingEvent::MessageDelta { .. }
454 | StreamingEvent::MessageStop
455 | StreamingEvent::Ping
456 | StreamingEvent::Error { .. } => {}
457 }
458 Ok(())
459 }
460
461 fn interpret_whole_message(
465 &mut self,
466 message: CompletionResponse,
467 mut out: Out<'id, Completion>,
468 ) -> Result<Flow, ProviderError> {
469 self.input_tokens = message.usage.input_tokens;
470 self.cache_creation
471 .clone_from(&message.usage.cache_creation);
472 self.message_id = Some(message.id);
473 self.response_model = Some(message.model);
474
475 let legal_empty_turn = match message.stop_reason.as_deref() {
477 Some("end_turn") => true,
478 Some("stop_sequence") => message.stop_sequence.is_some(),
479 _ => false,
480 };
481 if message.content.is_empty() && !legal_empty_turn {
482 return Err(ProviderError::Response(
483 crate::message::EMPTY_RESPONSE_ERROR.to_owned(),
484 ));
485 }
486
487 for (index, content) in message.content.into_iter().enumerate() {
488 let delta = match &content {
492 Content::Text { text, .. } if !text.is_empty() => {
493 Some(ContentDelta::TextDelta { text: text.clone() })
494 }
495 Content::ToolUse { input, .. } => Some(ContentDelta::InputJsonDelta {
496 partial_json: input.to_string(),
497 }),
498 _ => None,
499 };
500 self.interpret_content(
501 StreamingEvent::ContentBlockStart {
502 index,
503 content_block: content,
504 },
505 &mut out,
506 )?;
507 if let Some(delta) = delta {
508 self.interpret_content(
509 StreamingEvent::ContentBlockDelta { index, delta },
510 &mut out,
511 )?;
512 }
513 self.interpret_content(StreamingEvent::ContentBlockStop { index }, &mut out)?;
514 }
515
516 let usage = PartialUsage {
519 output_tokens: message.usage.output_tokens as usize,
520 input_tokens: usize::try_from(message.usage.input_tokens).ok(),
521 cache_creation_input_tokens: message.usage.cache_creation_input_tokens,
522 cache_creation: message.usage.cache_creation,
523 cache_read_input_tokens: message.usage.cache_read_input_tokens,
524 output_tokens_details: message.usage.output_tokens_details,
525 };
526 let native = StreamingCompletionResponse {
527 usage,
528 stop_reason: message.stop_reason,
529 stop_sequence: message.stop_sequence,
530 message_id: self.message_id.clone(),
531 model: self.response_model.clone(),
532 };
533 Ok(out.end(finish_of(&native)))
534 }
535}
536
537impl<'id> Decoder<'id, Completion> for MessagesDecoder<'id> {
538 type Event = StreamingEvent;
539
540 fn classify(&self, frame: WireFrame) -> WireEvent<StreamingEvent> {
541 let data = frame.as_str();
542 wire::classify_tagged_frame(&data, "type", |event_type| {
543 KNOWN_EVENT_TYPES.contains(&event_type)
544 })
545 .map(|event| match event {
546 StreamingEvent::Error { error, .. } => StreamingEvent::Error {
550 error,
551 raw: data.to_string(),
552 },
553 other => other,
554 })
555 }
556
557 fn decode(
558 &mut self,
559 event: StreamingEvent,
560 mut out: Out<'id, Completion>,
561 ) -> Result<Flow, ProviderError> {
562 match event {
563 StreamingEvent::Message { message } => self.interpret_whole_message(message, out),
564 StreamingEvent::MessageStart { message } => {
565 if let Some(message) = message {
568 self.input_tokens = message.usage.input_tokens;
569 self.cache_creation
570 .clone_from(&message.usage.cache_creation);
571 self.cache_read_input_tokens = message.usage.cache_read_input_tokens;
572 self.cache_creation_input_tokens = message.usage.cache_creation_input_tokens;
573 self.message_id = Some(message.id.clone());
574 self.response_model = Some(message.model.clone());
575 }
576 Ok(Flow::More)
577 }
578 StreamingEvent::MessageDelta { delta, usage } => {
579 let Some(reason) = delta.stop_reason else {
582 return Ok(Flow::More);
583 };
584 let usage = PartialUsage {
587 output_tokens: usage.output_tokens,
588 input_tokens: usage
589 .input_tokens
590 .filter(|tokens| *tokens > 0)
591 .or_else(|| usize::try_from(self.input_tokens).ok()),
592 cache_creation_input_tokens: usage
595 .cache_creation_input_tokens
596 .or(self.cache_creation_input_tokens),
597 cache_creation: usage.cache_creation.or(self.cache_creation),
598 cache_read_input_tokens: usage
599 .cache_read_input_tokens
600 .or(self.cache_read_input_tokens),
601 output_tokens_details: usage.output_tokens_details,
603 };
604 let native = StreamingCompletionResponse {
605 usage,
606 stop_reason: Some(reason),
607 stop_sequence: delta.stop_sequence,
611 message_id: self.message_id.clone(),
612 model: self.response_model.clone(),
613 };
614 out.raw(serde_json::to_value(&native)?);
615 Ok(out.end(finish_of(&native)))
616 }
617 StreamingEvent::Error { raw, .. } => {
618 Err(crate::error::ProviderError::from_provider_body(raw))
620 }
621 event @ (StreamingEvent::ContentBlockStart { .. }
622 | StreamingEvent::ContentBlockDelta { .. }
623 | StreamingEvent::ContentBlockStop { .. }
624 | StreamingEvent::MessageStop
625 | StreamingEvent::Ping) => {
626 self.interpret_content(event, &mut out)?;
627 Ok(Flow::More)
628 }
629 }
630 }
631}
632
633impl MessagesDecoder<'_> {
634 pub(crate) fn project(payload: &[u8], sink: &mut ObservationSink<'_>) {
639 let Ok(payload) = serde_json::from_slice::<ObservedPayload>(payload) else {
640 return;
641 };
642 let usage = payload.usage;
643 let (id, model, stop_reason, nested_usage) = match payload.message {
644 Some(message) => (
645 message.id,
646 message.model,
647 message.stop_reason,
648 message.usage,
649 ),
650 None => (payload.id, payload.model, payload.stop_reason, None),
651 };
652 if let Some(usage) = usage.or(nested_usage) {
656 sink.emit(AdapterEvent::Usage {
657 usage: AdapterUsage {
658 input_tokens: usage.input_tokens,
659 output_tokens: usage.output_tokens,
660 total_tokens: None,
661 cached_input_tokens: usage.cache_read_input_tokens,
662 reasoning_tokens: usage
663 .output_tokens_details
664 .map(|details| details.thinking_tokens),
665 tool_input_tokens: None,
666 },
667 });
668 }
669 let stop_reason = stop_reason.or(payload.delta.and_then(|delta| delta.stop_reason));
670 let verdict = AdapterVerdict {
671 finish_reason: stop_reason.map(|v| sink.scrub(&v)),
672 block_reason: None,
673 detail: None,
674 model: model.map(|v| sink.scrub(&v)),
675 };
676 let response_id = id.map(|v| sink.scrub(&v));
677 sink.provider(verdict, response_id);
678 if let Some(error) = payload.error {
679 error.emit(sink);
680 }
681 }
682}
683
684#[derive(Deserialize)]
686struct ObservedPayload {
687 id: Option<String>,
688 model: Option<String>,
689 stop_reason: Option<String>,
690 usage: Option<ObservedUsage>,
691 message: Option<Box<ObservedPayload>>,
692 delta: Option<ObservedDelta>,
693 error: Option<ObservedError>,
694}
695
696#[derive(Deserialize)]
697struct ObservedUsage {
698 #[serde(default, deserialize_with = "crate::observe::lenient_count")]
699 input_tokens: Option<u64>,
700 #[serde(default, deserialize_with = "crate::observe::lenient_count")]
701 output_tokens: Option<u64>,
702 #[serde(default, deserialize_with = "crate::observe::lenient_count")]
703 cache_read_input_tokens: Option<u64>,
704 #[serde(default)]
705 output_tokens_details: Option<ObservedOutputDetails>,
706}
707
708#[derive(Deserialize)]
709struct ObservedOutputDetails {
710 #[serde(default)]
711 thinking_tokens: u64,
712}
713
714#[derive(Deserialize)]
715struct ObservedDelta {
716 stop_reason: Option<String>,
717}
718
719#[derive(Clone, Debug, Default, Deserialize, Serialize)]
722pub struct StreamingCompletionResponse {
723 pub usage: PartialUsage,
725 #[serde(default, skip_serializing_if = "Option::is_none")]
727 pub stop_reason: Option<String>,
728 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub stop_sequence: Option<String>,
732 #[serde(default, skip_serializing_if = "Option::is_none")]
734 pub message_id: Option<String>,
735 #[serde(default, skip_serializing_if = "Option::is_none")]
737 pub model: Option<String>,
738}
739
740fn finish_of(response: &StreamingCompletionResponse) -> Finish {
742 Finish {
743 usage: crate::completion::Usage::from(&response.usage),
744 reason: response.stop_reason.as_deref().map(map_finish_reason),
745 message_id: response.message_id.clone(),
746 model: response.model.clone(),
747 ..Finish::default()
748 }
749}
750
751#[cfg(test)]
752mod tests;