lutum_protocol/
transcript.rs1use std::{any::Any, fmt, sync::Arc};
2
3use crate::conversation::{AssistantTurnItem, RawJson, ToolCallId, ToolName};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum TurnRole {
8 System,
9 Developer,
10 User,
11 Assistant,
12}
13
14pub struct ToolCallItemView<'a> {
16 pub id: &'a ToolCallId,
17 pub name: &'a ToolName,
18 pub arguments: &'a RawJson,
19}
20
21pub struct ToolResultItemView<'a> {
23 pub id: &'a ToolCallId,
24 pub name: &'a ToolName,
25 pub arguments: &'a RawJson,
26 pub result: &'a RawJson,
27}
28
29pub trait ItemView {
34 fn as_text(&self) -> Option<&str>;
36
37 fn as_reasoning(&self) -> Option<&str>;
39
40 fn as_refusal(&self) -> Option<&str>;
42
43 fn as_tool_call(&self) -> Option<ToolCallItemView<'_>>;
45
46 fn as_tool_result(&self) -> Option<ToolResultItemView<'_>>;
48}
49
50pub trait TurnView: fmt::Debug + Send + Sync {
54 fn role(&self) -> TurnRole;
56
57 fn item_count(&self) -> usize;
59
60 fn item_at(&self, index: usize) -> Option<&dyn ItemView>;
62
63 fn as_any(&self) -> &dyn Any;
65}
66
67impl dyn TurnView {
68 pub fn downcast_ref<T: TurnView + 'static>(&self) -> Option<&T> {
69 self.as_any().downcast_ref::<T>()
70 }
71}
72
73pub struct TurnItemIter<'a> {
77 turn: &'a dyn TurnView,
78 index: usize,
79}
80
81impl<'a> TurnItemIter<'a> {
82 pub fn new(turn: &'a dyn TurnView) -> Self {
83 Self { turn, index: 0 }
84 }
85}
86
87impl<'a> Iterator for TurnItemIter<'a> {
88 type Item = &'a dyn ItemView;
89
90 fn next(&mut self) -> Option<Self::Item> {
91 let item = self.turn.item_at(self.index)?;
92 self.index += 1;
93 Some(item)
94 }
95}
96
97#[derive(Debug)]
105pub struct AssistantTurnView {
106 items: Vec<CoreAssistantItemView>,
107}
108
109impl AssistantTurnView {
110 pub fn from_items(items: &[AssistantTurnItem]) -> Self {
112 Self {
113 items: items.iter().map(CoreAssistantItemView::from_item).collect(),
114 }
115 }
116}
117
118impl TurnView for AssistantTurnView {
119 fn role(&self) -> TurnRole {
120 TurnRole::Assistant
121 }
122
123 fn item_count(&self) -> usize {
124 self.items.len()
125 }
126
127 fn item_at(&self, index: usize) -> Option<&dyn ItemView> {
128 self.items.get(index).map(|v| v as &dyn ItemView)
129 }
130
131 fn as_any(&self) -> &dyn Any {
132 self
133 }
134}
135
136#[derive(Debug)]
137enum CoreAssistantItemKind {
138 Text(String),
139 Reasoning(String),
140 Refusal(String),
141 ToolCall {
142 id: ToolCallId,
143 name: ToolName,
144 arguments: RawJson,
145 },
146}
147
148#[derive(Debug)]
149struct CoreAssistantItemView {
150 kind: CoreAssistantItemKind,
151}
152
153impl CoreAssistantItemView {
154 fn from_item(item: &AssistantTurnItem) -> Self {
155 let kind = match item {
156 AssistantTurnItem::Text(t) => CoreAssistantItemKind::Text(t.clone()),
157 AssistantTurnItem::Reasoning(t) => CoreAssistantItemKind::Reasoning(t.clone()),
158 AssistantTurnItem::Refusal(t) => CoreAssistantItemKind::Refusal(t.clone()),
159 AssistantTurnItem::ToolCall {
160 id,
161 name,
162 arguments,
163 } => CoreAssistantItemKind::ToolCall {
164 id: id.clone(),
165 name: name.clone(),
166 arguments: arguments.clone(),
167 },
168 };
169 Self { kind }
170 }
171}
172
173impl ItemView for CoreAssistantItemView {
174 fn as_text(&self) -> Option<&str> {
175 match &self.kind {
176 CoreAssistantItemKind::Text(t) => Some(t),
177 _ => None,
178 }
179 }
180
181 fn as_reasoning(&self) -> Option<&str> {
182 match &self.kind {
183 CoreAssistantItemKind::Reasoning(t) => Some(t),
184 _ => None,
185 }
186 }
187
188 fn as_refusal(&self) -> Option<&str> {
189 match &self.kind {
190 CoreAssistantItemKind::Refusal(t) => Some(t),
191 _ => None,
192 }
193 }
194
195 fn as_tool_call(&self) -> Option<ToolCallItemView<'_>> {
196 match &self.kind {
197 CoreAssistantItemKind::ToolCall {
198 id,
199 name,
200 arguments,
201 } => Some(ToolCallItemView {
202 id,
203 name,
204 arguments,
205 }),
206 _ => None,
207 }
208 }
209
210 fn as_tool_result(&self) -> Option<ToolResultItemView<'_>> {
211 None }
213}
214
215pub type CommittedTurn = Arc<dyn TurnView + Send + Sync>;