1use objectiveai_sdk::agent::completions::message::{RichContent, RichContentPart};
20use objectiveai_sdk::agent::completions::response::ToolResponse;
21use objectiveai_sdk::agent::completions::response::streaming::{
22 AgentCompletionChunk, AssistantResponseChunk, MessageChunk,
23};
24use objectiveai_sdk::functions::executions::response::streaming::{
25 FunctionExecutionChunk, TaskChunk,
26};
27use objectiveai_sdk::vector::completions::response::streaming::VectorCompletionChunk;
28
29use super::row::{RowValue, RowsIter};
30
31pub fn agent_completion_chunk_rows<'a>(
35 chunk: &'a AgentCompletionChunk,
36) -> RowsIter<'a> {
37 let response_id = chunk.id.as_str();
38 let agent_hierarchy = chunk.agent_instance_hierarchy.as_str();
39 Box::new(
40 chunk
41 .messages
42 .iter()
43 .flat_map(move |msg| message_chunk_rows(response_id, agent_hierarchy, msg)),
44 )
45}
46
47pub fn vector_completion_chunk_rows<'a>(
50 chunk: &'a VectorCompletionChunk,
51) -> RowsIter<'a> {
52 Box::new(
53 chunk
54 .completions
55 .iter()
56 .flat_map(|c| agent_completion_chunk_rows(&c.inner)),
57 )
58}
59
60pub fn function_execution_chunk_rows<'a>(
65 chunk: &'a FunctionExecutionChunk,
66) -> RowsIter<'a> {
67 let task_iter = chunk
68 .tasks
69 .iter()
70 .flat_map(|task| task_chunk_rows(task));
71 let reasoning_iter = chunk
72 .reasoning
73 .iter()
74 .flat_map(|r| agent_completion_chunk_rows(&r.inner));
75 Box::new(task_iter.chain(reasoning_iter))
76}
77
78fn task_chunk_rows<'a>(task: &'a TaskChunk) -> RowsIter<'a> {
81 match task {
82 TaskChunk::FunctionExecution(wrapper) => {
83 function_execution_chunk_rows(&wrapper.inner)
84 }
85 TaskChunk::VectorCompletion(wrapper) => {
86 vector_completion_chunk_rows(&wrapper.inner)
87 }
88 }
89}
90
91fn message_chunk_rows<'a>(
92 response_id: &'a str,
93 agent_instance_hierarchy: &'a str,
94 msg: &'a MessageChunk,
95) -> RowsIter<'a> {
96 match msg {
97 MessageChunk::Assistant(a) => {
98 assistant_response_chunk_rows(response_id, agent_instance_hierarchy, a)
99 }
100 MessageChunk::Tool(t) => {
101 tool_response_rows(response_id, agent_instance_hierarchy, t)
102 }
103 }
104}
105
106fn assistant_response_chunk_rows<'a>(
107 response_id: &'a str,
108 agent_instance_hierarchy: &'a str,
109 chunk: &'a AssistantResponseChunk,
110) -> RowsIter<'a> {
111 let index = chunk.index;
112
113 let message_queue_iter = chunk
118 .request_message_ids
119 .as_deref()
120 .unwrap_or(&[])
121 .iter()
122 .copied()
123 .map(move |message_queue_content_id| RowValue::MessageQueueContent {
124 response_id,
125 agent_instance_hierarchy,
126 message_queue_content_id,
127 });
128
129 let reasoning_iter = chunk.reasoning.iter().map(move |text| {
135 RowValue::AssistantResponseReasoning {
136 response_id,
137 agent_instance_hierarchy,
138 index,
139 text: text.as_str(),
140 }
141 });
142 let tool_calls_iter = chunk
143 .tool_calls
144 .iter()
145 .flatten()
146 .filter_map(move |tc| {
147 let id = tc.id.as_deref()?;
148 let name = tc.function.as_ref().and_then(|f| f.name.as_deref())?;
149 let args = tc.function.as_ref().and_then(|f| f.arguments.as_deref())?;
150 Some(RowValue::AssistantResponseToolCalls {
151 response_id,
152 agent_instance_hierarchy,
153 index,
154 tool_call_index: tc.index,
163 tool_call_id: id,
164 function_name: name,
165 arguments: args,
166 })
167 });
168 let content_iter = chunk
169 .content
170 .iter()
171 .flat_map(move |c| assistant_content_rows(response_id, agent_instance_hierarchy, index, c));
172 let refusal_iter = chunk.refusal.iter().map(move |text| {
173 RowValue::AssistantResponseRefusal {
174 response_id,
175 agent_instance_hierarchy,
176 index,
177 text: text.as_str(),
178 }
179 });
180
181 Box::new(
182 message_queue_iter
183 .chain(reasoning_iter)
184 .chain(tool_calls_iter)
185 .chain(content_iter)
186 .chain(refusal_iter),
187 )
188}
189
190fn tool_response_rows<'a>(
191 response_id: &'a str,
192 agent_instance_hierarchy: &'a str,
193 response: &'a ToolResponse,
194) -> RowsIter<'a> {
195 let index = response.index;
196 let message_queue_iter = response
201 .request_message_ids
202 .as_deref()
203 .unwrap_or(&[])
204 .iter()
205 .copied()
206 .map(move |message_queue_content_id| RowValue::MessageQueueContent {
207 response_id,
208 agent_instance_hierarchy,
209 message_queue_content_id,
210 });
211 let head = std::iter::once(RowValue::ToolResponse {
212 response_id,
213 agent_instance_hierarchy,
214 index,
215 tool_call_id: response.inner.tool_call_id.as_str(),
216 });
217 Box::new(
218 message_queue_iter.chain(head).chain(tool_content_rows(
219 response_id,
220 agent_instance_hierarchy,
221 index,
222 &response.inner.content,
223 )),
224 )
225}
226
227fn assistant_content_rows<'a>(
228 response_id: &'a str,
229 agent_instance_hierarchy: &'a str,
230 index: u64,
231 content: &'a RichContent,
232) -> RowsIter<'a> {
233 match content {
234 RichContent::Text(text) => Box::new(std::iter::once(RowValue::AssistantResponseContentText {
235 response_id,
236 agent_instance_hierarchy,
237 index,
238 part_index: 0,
239 text: text.as_str(),
240 })),
241 RichContent::Parts(parts) => Box::new(parts.iter().enumerate().map(move |(part_index, part)| {
242 assistant_content_part(response_id, agent_instance_hierarchy, index, part_index as u64, part)
243 })),
244 }
245}
246
247fn assistant_content_part<'a>(
248 response_id: &'a str,
249 agent_instance_hierarchy: &'a str,
250 index: u64,
251 part_index: u64,
252 part: &'a RichContentPart,
253) -> RowValue<'a> {
254 match part {
255 RichContentPart::Text { text } => RowValue::AssistantResponseContentText {
256 response_id, agent_instance_hierarchy, index, part_index, text: text.as_str(),
257 },
258 RichContentPart::ImageUrl { image_url } => RowValue::AssistantResponseContentImage {
259 response_id, agent_instance_hierarchy, index, part_index, image_url,
260 },
261 RichContentPart::InputAudio { input_audio } => RowValue::AssistantResponseContentAudio {
262 response_id, agent_instance_hierarchy, index, part_index, input_audio,
263 },
264 RichContentPart::InputVideo { video_url }
265 | RichContentPart::VideoUrl { video_url } => RowValue::AssistantResponseContentVideo {
266 response_id, agent_instance_hierarchy, index, part_index, video_url,
267 },
268 RichContentPart::File { file } => RowValue::AssistantResponseContentFile {
269 response_id, agent_instance_hierarchy, index, part_index, file,
270 },
271 }
272}
273
274fn tool_content_rows<'a>(
275 response_id: &'a str,
276 agent_instance_hierarchy: &'a str,
277 index: u64,
278 content: &'a RichContent,
279) -> RowsIter<'a> {
280 match content {
281 RichContent::Text(text) => Box::new(std::iter::once(RowValue::ToolResponseContentText {
282 response_id,
283 agent_instance_hierarchy,
284 index,
285 part_index: 0,
286 text: text.as_str(),
287 })),
288 RichContent::Parts(parts) => Box::new(parts.iter().enumerate().map(move |(part_index, part)| {
289 tool_content_part(response_id, agent_instance_hierarchy, index, part_index as u64, part)
290 })),
291 }
292}
293
294fn tool_content_part<'a>(
295 response_id: &'a str,
296 agent_instance_hierarchy: &'a str,
297 index: u64,
298 part_index: u64,
299 part: &'a RichContentPart,
300) -> RowValue<'a> {
301 match part {
302 RichContentPart::Text { text } => RowValue::ToolResponseContentText {
303 response_id, agent_instance_hierarchy, index, part_index, text: text.as_str(),
304 },
305 RichContentPart::ImageUrl { image_url } => RowValue::ToolResponseContentImage {
306 response_id, agent_instance_hierarchy, index, part_index, image_url,
307 },
308 RichContentPart::InputAudio { input_audio } => RowValue::ToolResponseContentAudio {
309 response_id, agent_instance_hierarchy, index, part_index, input_audio,
310 },
311 RichContentPart::InputVideo { video_url }
312 | RichContentPart::VideoUrl { video_url } => RowValue::ToolResponseContentVideo {
313 response_id, agent_instance_hierarchy, index, part_index, video_url,
314 },
315 RichContentPart::File { file } => RowValue::ToolResponseContentFile {
316 response_id, agent_instance_hierarchy, index, part_index, file,
317 },
318 }
319}