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 .enumerate()
147 .filter_map(move |(tc_idx, tc)| {
148 let id = tc.id.as_deref()?;
149 let name = tc.function.as_ref().and_then(|f| f.name.as_deref())?;
150 let args = tc.function.as_ref().and_then(|f| f.arguments.as_deref())?;
151 Some(RowValue::AssistantResponseToolCalls {
152 response_id,
153 agent_instance_hierarchy,
154 index,
155 tool_call_index: tc_idx as u64,
156 tool_call_id: id,
157 function_name: name,
158 arguments: args,
159 })
160 });
161 let content_iter = chunk
162 .content
163 .iter()
164 .flat_map(move |c| assistant_content_rows(response_id, agent_instance_hierarchy, index, c));
165 let refusal_iter = chunk.refusal.iter().map(move |text| {
166 RowValue::AssistantResponseRefusal {
167 response_id,
168 agent_instance_hierarchy,
169 index,
170 text: text.as_str(),
171 }
172 });
173
174 Box::new(
175 message_queue_iter
176 .chain(reasoning_iter)
177 .chain(tool_calls_iter)
178 .chain(content_iter)
179 .chain(refusal_iter),
180 )
181}
182
183fn tool_response_rows<'a>(
184 response_id: &'a str,
185 agent_instance_hierarchy: &'a str,
186 response: &'a ToolResponse,
187) -> RowsIter<'a> {
188 let index = response.index;
189 let message_queue_iter = response
194 .request_message_ids
195 .as_deref()
196 .unwrap_or(&[])
197 .iter()
198 .copied()
199 .map(move |message_queue_content_id| RowValue::MessageQueueContent {
200 response_id,
201 agent_instance_hierarchy,
202 message_queue_content_id,
203 });
204 let head = std::iter::once(RowValue::ToolResponse {
205 response_id,
206 agent_instance_hierarchy,
207 index,
208 tool_call_id: response.inner.tool_call_id.as_str(),
209 });
210 Box::new(
211 message_queue_iter.chain(head).chain(tool_content_rows(
212 response_id,
213 agent_instance_hierarchy,
214 index,
215 &response.inner.content,
216 )),
217 )
218}
219
220fn assistant_content_rows<'a>(
221 response_id: &'a str,
222 agent_instance_hierarchy: &'a str,
223 index: u64,
224 content: &'a RichContent,
225) -> RowsIter<'a> {
226 match content {
227 RichContent::Text(text) => Box::new(std::iter::once(RowValue::AssistantResponseContentText {
228 response_id,
229 agent_instance_hierarchy,
230 index,
231 part_index: 0,
232 text: text.as_str(),
233 })),
234 RichContent::Parts(parts) => Box::new(parts.iter().enumerate().map(move |(part_index, part)| {
235 assistant_content_part(response_id, agent_instance_hierarchy, index, part_index as u64, part)
236 })),
237 }
238}
239
240fn assistant_content_part<'a>(
241 response_id: &'a str,
242 agent_instance_hierarchy: &'a str,
243 index: u64,
244 part_index: u64,
245 part: &'a RichContentPart,
246) -> RowValue<'a> {
247 match part {
248 RichContentPart::Text { text } => RowValue::AssistantResponseContentText {
249 response_id, agent_instance_hierarchy, index, part_index, text: text.as_str(),
250 },
251 RichContentPart::ImageUrl { image_url } => RowValue::AssistantResponseContentImage {
252 response_id, agent_instance_hierarchy, index, part_index, image_url,
253 },
254 RichContentPart::InputAudio { input_audio } => RowValue::AssistantResponseContentAudio {
255 response_id, agent_instance_hierarchy, index, part_index, input_audio,
256 },
257 RichContentPart::InputVideo { video_url }
258 | RichContentPart::VideoUrl { video_url } => RowValue::AssistantResponseContentVideo {
259 response_id, agent_instance_hierarchy, index, part_index, video_url,
260 },
261 RichContentPart::File { file } => RowValue::AssistantResponseContentFile {
262 response_id, agent_instance_hierarchy, index, part_index, file,
263 },
264 }
265}
266
267fn tool_content_rows<'a>(
268 response_id: &'a str,
269 agent_instance_hierarchy: &'a str,
270 index: u64,
271 content: &'a RichContent,
272) -> RowsIter<'a> {
273 match content {
274 RichContent::Text(text) => Box::new(std::iter::once(RowValue::ToolResponseContentText {
275 response_id,
276 agent_instance_hierarchy,
277 index,
278 part_index: 0,
279 text: text.as_str(),
280 })),
281 RichContent::Parts(parts) => Box::new(parts.iter().enumerate().map(move |(part_index, part)| {
282 tool_content_part(response_id, agent_instance_hierarchy, index, part_index as u64, part)
283 })),
284 }
285}
286
287fn tool_content_part<'a>(
288 response_id: &'a str,
289 agent_instance_hierarchy: &'a str,
290 index: u64,
291 part_index: u64,
292 part: &'a RichContentPart,
293) -> RowValue<'a> {
294 match part {
295 RichContentPart::Text { text } => RowValue::ToolResponseContentText {
296 response_id, agent_instance_hierarchy, index, part_index, text: text.as_str(),
297 },
298 RichContentPart::ImageUrl { image_url } => RowValue::ToolResponseContentImage {
299 response_id, agent_instance_hierarchy, index, part_index, image_url,
300 },
301 RichContentPart::InputAudio { input_audio } => RowValue::ToolResponseContentAudio {
302 response_id, agent_instance_hierarchy, index, part_index, input_audio,
303 },
304 RichContentPart::InputVideo { video_url }
305 | RichContentPart::VideoUrl { video_url } => RowValue::ToolResponseContentVideo {
306 response_id, agent_instance_hierarchy, index, part_index, video_url,
307 },
308 RichContentPart::File { file } => RowValue::ToolResponseContentFile {
309 response_id, agent_instance_hierarchy, index, part_index, file,
310 },
311 }
312}