Skip to main content

rig_core/providers/anthropic/streaming/
document.rs

1//! The Messages reassembler: a streamed reply rebuilt into the `Message` a
2//! unary reply is, so one reading of `raw` serves both.
3
4use std::collections::BTreeMap;
5
6use serde_json::{Map, Value, json};
7
8use super::MessagesEvent;
9use crate::wire::document::{Reassemble, keep, slot};
10
11/// Rebuilds the `Message` a streamed Messages reply adds up to.
12///
13/// `message_start.message` is the skeleton. Each `content_block_start`
14/// places its block at its index in `content`. Text, thinking and signature
15/// deltas append; `input_json_delta` fragments are parsed into the block's
16/// `input` at `content_block_stop` (`{}` when none streamed); a
17/// `citations_delta` appends to `citations`; any other delta merges into
18/// its block as the decoder merges it. `message_delta` folds its fields
19/// and its cumulative `usage` over the skeleton. A whole `message` frame is
20/// the document. A reply that failed or was cut short yields the message
21/// so far, and one that stated none yields `Null`.
22#[derive(Debug, Default)]
23pub struct Message {
24    /// The message so far; `None` until a frame states part of it.
25    message: Option<Map<String, Value>>,
26    /// The input JSON streamed to each block not yet stopped.
27    inputs: BTreeMap<usize, String>,
28}
29
30impl Message {
31    fn message(&mut self) -> &mut Map<String, Value> {
32        self.message.get_or_insert_with(Map::new)
33    }
34
35    /// The block at `index` in `content`, when one was placed there.
36    fn block(&mut self, index: usize) -> Option<&mut Map<String, Value>> {
37        self.message
38            .as_mut()?
39            .get_mut("content")?
40            .as_array_mut()?
41            .get_mut(index)?
42            .as_object_mut()
43    }
44
45    /// Place `block` at `index` in `content`, padding any gap with `null`.
46    fn place(&mut self, index: usize, block: Map<String, Value>) {
47        if let Value::Array(content) = slot(self.message(), "content", Value::Array) {
48            if content.len() <= index {
49                content.resize(index + 1, Value::Null);
50            }
51            if let Some(slot) = content.get_mut(index) {
52                *slot = Value::Object(block);
53            }
54        }
55    }
56
57    fn delta(&mut self, index: usize, delta: &Map<String, Value>) {
58        let kind = delta
59            .get("type")
60            .and_then(Value::as_str)
61            .unwrap_or_default();
62        // A gateway that skips `content_block_start` still streams the
63        // block's text, so the first delta opens it, as the decoder does.
64        if self.block(index).is_none() {
65            let opened = match kind {
66                "text_delta" => json!({"type": "text", "text": ""}),
67                "thinking_delta" => json!({"type": "thinking", "thinking": ""}),
68                _ => Value::Null,
69            };
70            if let Value::Object(block) = opened {
71                self.place(index, block);
72            }
73        }
74        if kind == "input_json_delta" {
75            if let Some(fragment) = delta.get("partial_json").and_then(Value::as_str) {
76                self.inputs.entry(index).or_default().push_str(fragment);
77            }
78            return;
79        }
80        let Some(block) = self.block(index) else {
81            return;
82        };
83        if kind == "citations_delta" {
84            let citation = delta.get("citation").cloned().unwrap_or_default();
85            match block.get_mut("citations") {
86                Some(Value::Array(citations)) => citations.push(citation),
87                _ => {
88                    block.insert("citations".to_owned(), Value::Array(vec![citation]));
89                }
90            }
91            return;
92        }
93        let mut item = Value::Object(std::mem::take(block));
94        crate::operation::completion::merge(&mut item, delta);
95        if let Value::Object(item) = item {
96            *block = item;
97        }
98    }
99
100    /// Set the `input` of the block at `index` from the JSON streamed to
101    /// it. Input that does not parse leaves the input the block opened
102    /// with.
103    fn settle(&mut self, index: usize) {
104        let Some(json) = self.inputs.remove(&index) else {
105            return;
106        };
107        let Ok(input) = crate::json_utils::parse_tool_arguments(&json) else {
108            return;
109        };
110        if let Some(block) = self.block(index) {
111            block.insert("input".to_owned(), input);
112        }
113    }
114
115    fn message_delta(&mut self, event: &MessagesEvent) {
116        if let Some(Value::Object(delta)) = event.fields.get("delta") {
117            let message = self.message();
118            for (key, value) in delta {
119                keep(message, key, value);
120            }
121        }
122        if let Some(Value::Object(terminal)) = event.fields.get("usage") {
123            let usage = self
124                .message()
125                .entry("usage")
126                .or_insert_with(|| Value::Object(Map::new()));
127            if !usage.is_object() {
128                *usage = Value::Object(Map::new());
129            }
130            if let Value::Object(usage) = usage {
131                // Zero input is how some gateways leave the count out of
132                // `message_delta`, as the decoder reads it.
133                let started = usage
134                    .get("input_tokens")
135                    .and_then(Value::as_u64)
136                    .is_some_and(|tokens| tokens > 0);
137                for (key, value) in terminal {
138                    if key == "input_tokens" && started && value.as_u64() == Some(0) {
139                        continue;
140                    }
141                    keep(usage, key, value);
142                }
143            }
144        }
145    }
146}
147
148impl crate::wire::document::Serves<crate::operation::Completion> for Message {}
149
150impl Reassemble<MessagesEvent> for Message {
151    fn absorb(&mut self, event: &MessagesEvent) {
152        match event.kind() {
153            "message" => {
154                if let Value::Object(message) = &event.fields {
155                    self.message = Some(message.clone());
156                    self.inputs.clear();
157                }
158            }
159            "message_start" => {
160                if let Some(Value::Object(start)) = event.fields.get("message") {
161                    let message = self.message();
162                    for (key, value) in start {
163                        keep(message, key, value);
164                    }
165                }
166            }
167            "content_block_start" => {
168                if let (Ok(index), Ok(block)) = (event.index(), event.item("content_block")) {
169                    self.inputs.remove(&index);
170                    self.place(index, block.clone());
171                }
172            }
173            "content_block_delta" => {
174                if let (Ok(index), Ok(delta)) = (event.index(), event.item("delta")) {
175                    self.delta(index, delta);
176                }
177            }
178            "content_block_stop" => {
179                if let Ok(index) = event.index() {
180                    self.settle(index);
181                }
182            }
183            "message_delta" => self.message_delta(event),
184            // `message_stop`, `ping` and `error` add nothing: a failed
185            // reply keeps the message so far.
186            _ => {}
187        }
188    }
189
190    fn finish(mut self) -> Value {
191        let open: Vec<usize> = self.inputs.keys().copied().collect();
192        for index in open {
193            self.settle(index);
194        }
195        self.message.map_or(Value::Null, Value::Object)
196    }
197}
198
199#[cfg(test)]
200mod tests;