whycodes_session/
pairing.rs1use whycodes_core::types::{ContentBlock, Message, MessageContent, Role};
12
13pub const MISSING_RESULT: &str = "Tool result missing: this call was interrupted and never ran.";
15
16pub fn paired_messages(messages: &[Message]) -> Option<Vec<Message>> {
19 let mut out: Vec<Message> = Vec::with_capacity(messages.len());
20 let mut open: Vec<String> = Vec::new();
21 let mut lenient = false;
24 let mut changed = false;
25 for msg in messages {
26 match msg.role {
27 Role::Tool => {
28 let id = msg.tool_call_id.as_deref().unwrap_or("");
29 let owned = take_open(&mut open, id) || lenient;
30 changed |= !owned;
31 push_if(&mut out, msg, owned);
32 }
33 Role::Assistant => {
34 changed |= close_open(&mut out, &mut open);
35 lenient = record_calls(msg, &mut open);
36 out.push(msg.clone());
37 }
38 Role::User | Role::System => {
39 changed |= close_open(&mut out, &mut open);
40 lenient = false;
41 out.push(msg.clone());
42 }
43 }
44 }
45 changed |= close_open(&mut out, &mut open);
46 changed.then_some(out)
47}
48
49fn take_open(open: &mut Vec<String>, id: &str) -> bool {
51 let before = open.len();
52 open.retain(|o| o != id);
53 open.len() != before
54}
55
56fn push_if(out: &mut Vec<Message>, msg: &Message, keep: bool) {
57 if keep {
58 out.push(msg.clone());
59 }
60}
61
62fn record_calls(msg: &Message, open: &mut Vec<String>) -> bool {
64 let MessageContent::Blocks(blocks) = &msg.content else {
65 return false;
66 };
67 let mut unnamed = false;
68 for block in blocks {
69 if let ContentBlock::ToolUse { id, .. } = block {
70 unnamed |= id.is_empty();
71 open.push(id.clone());
72 }
73 }
74 open.retain(|id| !id.is_empty());
75 unnamed
76}
77
78fn close_open(out: &mut Vec<Message>, open: &mut Vec<String>) -> bool {
80 let any = !open.is_empty();
81 for id in open.drain(..) {
82 out.push(Message {
83 role: Role::Tool,
84 content: MessageContent::Text(MISSING_RESULT.to_string()),
85 tool_call_id: Some(id),
86 name: None,
87 created_at: None,
88 });
89 }
90 any
91}
92
93#[cfg(test)]
94#[path = "pairing_tests.rs"]
95mod tests;