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, BTreeSet};
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    closed: BTreeSet<usize>,
29    starts: BTreeMap<usize, Vec<Map<String, Value>>>,
30    positions: BTreeMap<usize, usize>,
31    reconnecting: bool,
32    replayed: BTreeSet<usize>,
33    message_started: bool,
34}
35
36impl Message {
37    fn message(&mut self) -> &mut Map<String, Value> {
38        self.message.get_or_insert_with(Map::new)
39    }
40
41    /// The block at `index` in `content`, when one was placed there.
42    fn block(&mut self, index: usize) -> Option<&mut Map<String, Value>> {
43        let index = self.positions.get(&index).copied()?;
44        self.message
45            .as_mut()?
46            .get_mut("content")?
47            .as_array_mut()?
48            .get_mut(index)?
49            .as_object_mut()
50    }
51
52    /// Place a new block in `content`, appending when its wire index was
53    /// already occupied and retaining the mapping for subsequent deltas.
54    fn place(&mut self, index: usize, block: Map<String, Value>) {
55        let position = match self.message().get("content").and_then(Value::as_array) {
56            Some(content) if content.get(index).is_some_and(|item| !item.is_null()) => {
57                content.len()
58            }
59            _ => index,
60        };
61        self.positions.insert(index, position);
62        let index = position;
63        if let Value::Array(content) = slot(self.message(), "content", Value::Array) {
64            if content.len() <= index {
65                content.resize(index + 1, Value::Null);
66            }
67            if let Some(slot) = content.get_mut(index) {
68                *slot = Value::Object(block);
69            }
70        }
71    }
72
73    fn delta(&mut self, index: usize, delta: &Map<String, Value>) {
74        let kind = delta
75            .get("type")
76            .and_then(Value::as_str)
77            .unwrap_or_default();
78        // A gateway that skips `content_block_start` still streams the
79        // block's text, so the first delta opens it, as the decoder does.
80        if self.block(index).is_none() {
81            let opened = match kind {
82                "text_delta" => json!({"type": "text", "text": ""}),
83                "thinking_delta" => json!({"type": "thinking", "thinking": ""}),
84                _ => Value::Null,
85            };
86            if let Value::Object(block) = opened {
87                self.place(index, block);
88            }
89        }
90        if kind == "input_json_delta" {
91            if let Some(fragment) = delta.get("partial_json").and_then(Value::as_str) {
92                self.inputs.entry(index).or_default().push_str(fragment);
93            }
94            return;
95        }
96        let Some(block) = self.block(index) else {
97            return;
98        };
99        if kind == "citations_delta" {
100            let citation = delta.get("citation").cloned().unwrap_or_default();
101            match block.get_mut("citations") {
102                Some(Value::Array(citations)) => citations.push(citation),
103                _ => {
104                    block.insert("citations".to_owned(), Value::Array(vec![citation]));
105                }
106            }
107            return;
108        }
109        let mut item = Value::Object(std::mem::take(block));
110        crate::operation::completion::merge(&mut item, delta);
111        if let Value::Object(item) = item {
112            *block = item;
113        }
114    }
115
116    /// Set the `input` of the block at `index` from the JSON streamed to
117    /// it. Input that does not parse leaves the input the block opened
118    /// with.
119    fn settle(&mut self, index: usize) {
120        let Some(json) = self.inputs.remove(&index) else {
121            return;
122        };
123        let Ok(input) = crate::json_utils::parse_tool_arguments(&json) else {
124            return;
125        };
126        if let Some(block) = self.block(index) {
127            block.insert("input".to_owned(), input);
128        }
129    }
130
131    fn message_delta(&mut self, event: &MessagesEvent) {
132        if let Some(Value::Object(delta)) = event.fields.get("delta") {
133            let message = self.message();
134            for (key, value) in delta {
135                keep(message, key, value);
136            }
137        }
138        if let Some(Value::Object(terminal)) = event.fields.get("usage") {
139            let usage = self
140                .message()
141                .entry("usage")
142                .or_insert_with(|| Value::Object(Map::new()));
143            if !usage.is_object() {
144                *usage = Value::Object(Map::new());
145            }
146            if let Value::Object(usage) = usage {
147                // Zero input is how some gateways leave the count out of
148                // `message_delta`, as the decoder reads it.
149                let started = usage
150                    .get("input_tokens")
151                    .and_then(Value::as_u64)
152                    .is_some_and(|tokens| tokens > 0);
153                for (key, value) in terminal {
154                    if key == "input_tokens" && started && value.as_u64() == Some(0) {
155                        continue;
156                    }
157                    keep(usage, key, value);
158                }
159            }
160        }
161    }
162}
163
164impl crate::wire::document::Serves<crate::operation::Completion> for Message {}
165
166impl Reassemble<MessagesEvent> for Message {
167    fn absorb(&mut self, event: &MessagesEvent) {
168        match event.kind() {
169            "message" => {
170                if let Value::Object(message) = &event.fields {
171                    self.message = Some(message.clone());
172                    self.inputs.clear();
173                }
174            }
175            "message_start" => {
176                if self.message_started && event.fields.get("message").is_some_and(Value::is_object)
177                {
178                    self.reconnecting = true;
179                }
180                if !self.message_started
181                    && let Some(Value::Object(start)) = event.fields.get("message")
182                {
183                    self.message_started = true;
184                    let message = self.message();
185                    for (key, value) in start {
186                        keep(message, key, value);
187                    }
188                }
189            }
190            "content_block_start" => {
191                if let (Ok(index), Ok(block)) = (event.index(), event.item("content_block")) {
192                    if self.closed.contains(&index) {
193                        if self.starts.get(&index).is_some_and(|previous| {
194                            super::replayed_start(previous, block, self.reconnecting)
195                        }) {
196                            self.replayed.insert(index);
197                            return;
198                        }
199                        self.closed.remove(&index);
200                        self.replayed.remove(&index);
201                        self.reconnecting = false;
202                        self.starts.entry(index).or_default().push(block.clone());
203                        self.place(index, block.clone());
204                    } else if self.block(index).is_none() {
205                        self.reconnecting = false;
206                        self.starts.entry(index).or_default().push(block.clone());
207                        self.place(index, block.clone());
208                    }
209                }
210            }
211            "content_block_delta" => {
212                if let (Ok(index), Ok(delta)) = (event.index(), event.item("delta"))
213                    && !self.replayed.contains(&index)
214                {
215                    self.delta(index, delta);
216                }
217            }
218            "content_block_stop" => {
219                if let Ok(index) = event.index()
220                    && !self.replayed.remove(&index)
221                {
222                    self.settle(index);
223                    self.closed.insert(index);
224                }
225            }
226            "message_delta" => self.message_delta(event),
227            // `message_stop`, `ping` and `error` add nothing: a failed
228            // reply keeps the message so far.
229            _ => {}
230        }
231    }
232
233    fn finish(mut self) -> Value {
234        let open: Vec<usize> = self.inputs.keys().copied().collect();
235        for index in open {
236            self.settle(index);
237        }
238        self.message.map_or(Value::Null, Value::Object)
239    }
240}
241
242#[cfg(test)]
243mod tests;