1use objectiveai_sdk::agent::completions::message::{File, ImageUrl, InputAudio, VideoUrl};
13use objectiveai_sdk::agent::completions::request::AgentCompletionCreateParams;
14use objectiveai_sdk::cli::command::agents::logs::open::Response;
15use objectiveai_sdk::functions::executions::request::FunctionExecutionCreateParams;
16use objectiveai_sdk::vector::completions::request::VectorCompletionCreateParams;
17use sqlx::Row as _;
18
19use super::super::{Error, Pool};
20use super::row::MessageTable;
21
22pub async fn read_by_id(pool: &Pool, id: i64) -> Result<Option<Response>, Error> {
26 let Some(msg) = sqlx::query(
27 "SELECT response_id, \"table\" AS table_kind, row_index, row_sub_index \
28 FROM objectiveai.messages \
29 WHERE \"index\" = $1",
30 )
31 .bind(id)
32 .fetch_optional(&**pool)
33 .await?
34 else {
35 return Ok(None);
36 };
37
38 let response_id: Option<String> = msg.try_get("response_id")?;
40 let response_id = response_id.unwrap_or_default();
41 let table_kind: MessageTable = msg.try_get("table_kind")?;
42 let row_index: Option<i64> = msg.try_get("row_index")?;
43 let row_sub_index: Option<i64> = msg.try_get("row_sub_index")?;
44
45 Ok(Some(load_payload(
46 pool,
47 table_kind,
48 &response_id,
49 row_index,
50 row_sub_index,
51 ).await?))
52}
53
54async fn load_payload(
55 pool: &Pool,
56 table_kind: MessageTable,
57 response_id: &str,
58 row_index: Option<i64>,
59 row_sub_index: Option<i64>,
60) -> Result<Response, Error> {
61 match table_kind {
62 MessageTable::MessageQueueText => {
69 let id = require_row_index(table_kind, row_index)?;
70 let text: String = sqlx::query_scalar(
71 "SELECT text FROM objectiveai.message_queue_texts WHERE id = $1",
72 )
73 .bind(id)
74 .fetch_one(&**pool)
75 .await?;
76 Ok(Response::Text { text })
77 }
78 MessageTable::MessageQueueImage => {
79 let id = require_row_index(table_kind, row_index)?;
80 let row = sqlx::query(
81 "SELECT url, detail FROM objectiveai.message_queue_images WHERE id = $1",
82 )
83 .bind(id)
84 .fetch_one(&**pool)
85 .await?;
86 let url: String = row.try_get("url")?;
87 let detail_str: Option<String> = row.try_get("detail")?;
88 let detail = match detail_str {
89 Some(s) => serde_json::from_value(serde_json::Value::String(s))?,
90 None => None,
91 };
92 Ok(Response::Image(ImageUrl { url, detail }))
93 }
94 MessageTable::MessageQueueAudio => {
95 let id = require_row_index(table_kind, row_index)?;
96 let row = sqlx::query(
97 "SELECT data, format FROM objectiveai.message_queue_audios WHERE id = $1",
98 )
99 .bind(id)
100 .fetch_one(&**pool)
101 .await?;
102 Ok(Response::Audio(InputAudio {
103 data: row.try_get("data")?,
104 format: row.try_get("format")?,
105 }))
106 }
107 MessageTable::MessageQueueVideo => {
108 let id = require_row_index(table_kind, row_index)?;
109 let url: String = sqlx::query_scalar(
110 "SELECT url FROM objectiveai.message_queue_videos WHERE id = $1",
111 )
112 .bind(id)
113 .fetch_one(&**pool)
114 .await?;
115 Ok(Response::Video(VideoUrl { url }))
116 }
117 MessageTable::MessageQueueFile => {
118 let id = require_row_index(table_kind, row_index)?;
119 let row = sqlx::query(
120 "SELECT file_data, file_id, filename, file_url \
121 FROM objectiveai.message_queue_files WHERE id = $1",
122 )
123 .bind(id)
124 .fetch_one(&**pool)
125 .await?;
126 Ok(Response::File(File {
127 file_data: row.try_get("file_data")?,
128 file_id: row.try_get("file_id")?,
129 filename: row.try_get("filename")?,
130 file_url: row.try_get("file_url")?,
131 }))
132 }
133 MessageTable::AgentCompletionRequest => {
134 let (body, created_at, sender) =
135 fetch_request_blob(pool, "objectiveai.agent_completion_requests", response_id).await?;
136 let body: AgentCompletionCreateParams = serde_json::from_value(body)?;
137 Ok(Response::AgentCompletionRequest {
138 response_id: response_id.to_string(),
139 sender_agent_instance_hierarchy: sender,
140 body,
141 created_at,
142 })
143 }
144 MessageTable::VectorCompletionRequest => {
145 let (body, created_at, sender) =
146 fetch_request_blob(pool, "objectiveai.vector_completion_requests", response_id).await?;
147 let body: VectorCompletionCreateParams = serde_json::from_value(body)?;
148 Ok(Response::VectorCompletionRequest {
149 response_id: response_id.to_string(),
150 sender_agent_instance_hierarchy: sender,
151 body,
152 created_at,
153 })
154 }
155 MessageTable::FunctionExecutionRequest => {
156 let (body, created_at, sender) =
157 fetch_request_blob(pool, "objectiveai.function_execution_requests", response_id).await?;
158 let body: FunctionExecutionCreateParams = serde_json::from_value(body)?;
159 Ok(Response::FunctionExecutionRequest {
160 response_id: response_id.to_string(),
161 sender_agent_instance_hierarchy: sender,
162 body,
163 created_at,
164 })
165 }
166 MessageTable::AssistantResponseRefusal => {
167 let index = require_row_index(table_kind, row_index)?;
168 let text = fetch_indexed_text(
169 pool,
170 "objectiveai.assistant_response_refusal",
171 response_id,
172 index,
173 )
174 .await?;
175 Ok(Response::Text { text })
176 }
177 MessageTable::AssistantResponseReasoning => {
178 let index = require_row_index(table_kind, row_index)?;
179 let text = fetch_indexed_text(
180 pool,
181 "objectiveai.assistant_response_reasoning",
182 response_id,
183 index,
184 )
185 .await?;
186 Ok(Response::Text { text })
187 }
188 MessageTable::AssistantResponseToolCalls => {
195 let index = require_row_index(table_kind, row_index)?;
196 let tool_call_index = require_row_sub_index(table_kind, row_sub_index)?;
197 let row = sqlx::query(
198 "SELECT arguments \
199 FROM objectiveai.assistant_response_tool_calls \
200 WHERE response_id = $1 AND \"index\" = $2 AND tool_call_index = $3",
201 )
202 .bind(response_id)
203 .bind(index)
204 .bind(tool_call_index)
205 .fetch_one(&**pool)
206 .await?;
207 Ok(Response::Text { text: row.try_get("arguments")? })
208 }
209 MessageTable::AssistantResponseContentText => {
210 let (index, part_index) =
211 require_full_indices(table_kind, row_index, row_sub_index)?;
212 let text = fetch_content_text(
213 pool,
214 "objectiveai.assistant_response_content_text",
215 response_id,
216 index,
217 part_index,
218 )
219 .await?;
220 Ok(Response::Text { text })
221 }
222 MessageTable::AssistantResponseContentImage => {
223 let (index, part_index) =
224 require_full_indices(table_kind, row_index, row_sub_index)?;
225 let image_url = fetch_content_image(
226 pool,
227 "objectiveai.assistant_response_content_image",
228 response_id,
229 index,
230 part_index,
231 )
232 .await?;
233 Ok(Response::Image(image_url))
234 }
235 MessageTable::AssistantResponseContentAudio => {
236 let (index, part_index) =
237 require_full_indices(table_kind, row_index, row_sub_index)?;
238 let input_audio = fetch_content_audio(
239 pool,
240 "objectiveai.assistant_response_content_audio",
241 response_id,
242 index,
243 part_index,
244 )
245 .await?;
246 Ok(Response::Audio(input_audio))
247 }
248 MessageTable::AssistantResponseContentVideo => {
249 let (index, part_index) =
250 require_full_indices(table_kind, row_index, row_sub_index)?;
251 let video_url = fetch_content_video(
252 pool,
253 "objectiveai.assistant_response_content_video",
254 response_id,
255 index,
256 part_index,
257 )
258 .await?;
259 Ok(Response::Video(video_url))
260 }
261 MessageTable::AssistantResponseContentFile => {
262 let (index, part_index) =
263 require_full_indices(table_kind, row_index, row_sub_index)?;
264 let file = fetch_content_file(
265 pool,
266 "objectiveai.assistant_response_content_file",
267 response_id,
268 index,
269 part_index,
270 )
271 .await?;
272 Ok(Response::File(file))
273 }
274 MessageTable::ToolResponseContentText => {
275 let (index, part_index) =
276 require_full_indices(table_kind, row_index, row_sub_index)?;
277 let text = fetch_content_text(
278 pool,
279 "objectiveai.tool_response_content_text",
280 response_id,
281 index,
282 part_index,
283 )
284 .await?;
285 Ok(Response::Text { text })
286 }
287 MessageTable::ToolResponseContentImage => {
288 let (index, part_index) =
289 require_full_indices(table_kind, row_index, row_sub_index)?;
290 let image_url = fetch_content_image(
291 pool,
292 "objectiveai.tool_response_content_image",
293 response_id,
294 index,
295 part_index,
296 )
297 .await?;
298 Ok(Response::Image(image_url))
299 }
300 MessageTable::ToolResponseContentAudio => {
301 let (index, part_index) =
302 require_full_indices(table_kind, row_index, row_sub_index)?;
303 let input_audio = fetch_content_audio(
304 pool,
305 "objectiveai.tool_response_content_audio",
306 response_id,
307 index,
308 part_index,
309 )
310 .await?;
311 Ok(Response::Audio(input_audio))
312 }
313 MessageTable::ToolResponseContentVideo => {
314 let (index, part_index) =
315 require_full_indices(table_kind, row_index, row_sub_index)?;
316 let video_url = fetch_content_video(
317 pool,
318 "objectiveai.tool_response_content_video",
319 response_id,
320 index,
321 part_index,
322 )
323 .await?;
324 Ok(Response::Video(video_url))
325 }
326 MessageTable::ToolResponseContentFile => {
327 let (index, part_index) =
328 require_full_indices(table_kind, row_index, row_sub_index)?;
329 let file = fetch_content_file(
330 pool,
331 "objectiveai.tool_response_content_file",
332 response_id,
333 index,
334 part_index,
335 )
336 .await?;
337 Ok(Response::File(file))
338 }
339 MessageTable::RequestMessageUserContentText => {
341 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
342 Ok(Response::Text { text: fetch_content_text(pool, "objectiveai.request_message_user_content_text", response_id, index, part_index).await? })
343 }
344 MessageTable::RequestMessageUserContentImage => {
345 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
346 Ok(Response::Image(fetch_content_image(pool, "objectiveai.request_message_user_content_image", response_id, index, part_index).await?))
347 }
348 MessageTable::RequestMessageUserContentAudio => {
349 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
350 Ok(Response::Audio(fetch_content_audio(pool, "objectiveai.request_message_user_content_audio", response_id, index, part_index).await?))
351 }
352 MessageTable::RequestMessageUserContentVideo => {
353 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
354 Ok(Response::Video(fetch_content_video(pool, "objectiveai.request_message_user_content_video", response_id, index, part_index).await?))
355 }
356 MessageTable::RequestMessageUserContentFile => {
357 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
358 Ok(Response::File(fetch_content_file(pool, "objectiveai.request_message_user_content_file", response_id, index, part_index).await?))
359 }
360 MessageTable::RequestMessageAssistantRefusal => {
362 let index = require_row_index(table_kind, row_index)?;
363 Ok(Response::Text { text: fetch_indexed_text(pool, "objectiveai.request_message_assistant_refusal", response_id, index).await? })
364 }
365 MessageTable::RequestMessageAssistantReasoning => {
366 let index = require_row_index(table_kind, row_index)?;
367 Ok(Response::Text { text: fetch_indexed_text(pool, "objectiveai.request_message_assistant_reasoning", response_id, index).await? })
368 }
369 MessageTable::RequestMessageAssistantToolCalls => {
370 let index = require_row_index(table_kind, row_index)?;
371 let tool_call_index = require_row_sub_index(table_kind, row_sub_index)?;
372 let row = sqlx::query(
373 "SELECT arguments FROM objectiveai.request_message_assistant_tool_calls \
374 WHERE response_id = $1 AND \"index\" = $2 AND tool_call_index = $3",
375 )
376 .bind(response_id).bind(index).bind(tool_call_index)
377 .fetch_one(&**pool).await?;
378 Ok(Response::Text { text: row.try_get("arguments")? })
379 }
380 MessageTable::RequestMessageAssistantContentText => {
381 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
382 Ok(Response::Text { text: fetch_content_text(pool, "objectiveai.request_message_assistant_content_text", response_id, index, part_index).await? })
383 }
384 MessageTable::RequestMessageAssistantContentImage => {
385 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
386 Ok(Response::Image(fetch_content_image(pool, "objectiveai.request_message_assistant_content_image", response_id, index, part_index).await?))
387 }
388 MessageTable::RequestMessageAssistantContentAudio => {
389 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
390 Ok(Response::Audio(fetch_content_audio(pool, "objectiveai.request_message_assistant_content_audio", response_id, index, part_index).await?))
391 }
392 MessageTable::RequestMessageAssistantContentVideo => {
393 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
394 Ok(Response::Video(fetch_content_video(pool, "objectiveai.request_message_assistant_content_video", response_id, index, part_index).await?))
395 }
396 MessageTable::RequestMessageAssistantContentFile => {
397 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
398 Ok(Response::File(fetch_content_file(pool, "objectiveai.request_message_assistant_content_file", response_id, index, part_index).await?))
399 }
400 MessageTable::RequestMessageToolContentText => {
402 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
403 Ok(Response::Text { text: fetch_content_text(pool, "objectiveai.request_message_tool_content_text", response_id, index, part_index).await? })
404 }
405 MessageTable::RequestMessageToolContentImage => {
406 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
407 Ok(Response::Image(fetch_content_image(pool, "objectiveai.request_message_tool_content_image", response_id, index, part_index).await?))
408 }
409 MessageTable::RequestMessageToolContentAudio => {
410 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
411 Ok(Response::Audio(fetch_content_audio(pool, "objectiveai.request_message_tool_content_audio", response_id, index, part_index).await?))
412 }
413 MessageTable::RequestMessageToolContentVideo => {
414 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
415 Ok(Response::Video(fetch_content_video(pool, "objectiveai.request_message_tool_content_video", response_id, index, part_index).await?))
416 }
417 MessageTable::RequestMessageToolContentFile => {
418 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
419 Ok(Response::File(fetch_content_file(pool, "objectiveai.request_message_tool_content_file", response_id, index, part_index).await?))
420 }
421 MessageTable::RequestVectorChoiceContentText => {
423 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
424 Ok(Response::Text { text: fetch_content_text(pool, "objectiveai.request_vector_choice_content_text", response_id, index, part_index).await? })
425 }
426 MessageTable::RequestVectorChoiceContentImage => {
427 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
428 Ok(Response::Image(fetch_content_image(pool, "objectiveai.request_vector_choice_content_image", response_id, index, part_index).await?))
429 }
430 MessageTable::RequestVectorChoiceContentAudio => {
431 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
432 Ok(Response::Audio(fetch_content_audio(pool, "objectiveai.request_vector_choice_content_audio", response_id, index, part_index).await?))
433 }
434 MessageTable::RequestVectorChoiceContentVideo => {
435 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
436 Ok(Response::Video(fetch_content_video(pool, "objectiveai.request_vector_choice_content_video", response_id, index, part_index).await?))
437 }
438 MessageTable::RequestVectorChoiceContentFile => {
439 let (index, part_index) = require_full_indices(table_kind, row_index, row_sub_index)?;
440 Ok(Response::File(fetch_content_file(pool, "objectiveai.request_vector_choice_content_file", response_id, index, part_index).await?))
441 }
442 MessageTable::ResponseVectorVote => {
446 let row = sqlx::query(
447 "SELECT vote FROM objectiveai.response_vector_vote \
448 WHERE response_id = $1 LIMIT 1",
449 )
450 .bind(response_id)
451 .fetch_one(&**pool)
452 .await?;
453 let vote: sqlx::types::Json<serde_json::Value> = row.try_get("vote")?;
454 Ok(Response::Text { text: vote.0.to_string() })
455 }
456 MessageTable::Error => {
457 let id = require_row_index(table_kind, row_index)?;
458 let row = sqlx::query("SELECT error FROM objectiveai.errors WHERE id = $1")
459 .bind(id)
460 .fetch_one(&**pool)
461 .await?;
462 let error: sqlx::types::Json<serde_json::Value> = row.try_get("error")?;
463 Ok(Response::Error { error: error.0 })
464 }
465 }
466}
467
468async fn fetch_request_blob(
469 pool: &Pool,
470 table: &str,
471 response_id: &str,
472) -> Result<(serde_json::Value, i64, String), Error> {
473 let sql = format!(
474 "SELECT body, created_at, sender_agent_instance_hierarchy \
475 FROM {table} WHERE response_id = $1",
476 );
477 let row = sqlx::query(&sql)
478 .bind(response_id)
479 .fetch_one(&**pool)
480 .await?;
481 let body: sqlx::types::Json<serde_json::Value> = row.try_get("body")?;
482 let created_at: i64 = row.try_get("created_at")?;
483 let sender: String = row.try_get("sender_agent_instance_hierarchy")?;
484 Ok((body.0, created_at, sender))
485}
486
487async fn fetch_indexed_text(
488 pool: &Pool,
489 table: &str,
490 response_id: &str,
491 index: i64,
492) -> Result<String, Error> {
493 let sql = format!(
494 "SELECT text FROM {table} \
495 WHERE response_id = $1 AND \"index\" = $2",
496 );
497 let row = sqlx::query(&sql)
498 .bind(response_id)
499 .bind(index)
500 .fetch_one(&**pool)
501 .await?;
502 Ok(row.try_get("text")?)
503}
504
505async fn fetch_content_text(
506 pool: &Pool,
507 table: &str,
508 response_id: &str,
509 index: i64,
510 part_index: i64,
511) -> Result<String, Error> {
512 let sql = format!(
513 "SELECT text FROM {table} \
514 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
515 );
516 let row = sqlx::query(&sql)
517 .bind(response_id)
518 .bind(index)
519 .bind(part_index)
520 .fetch_one(&**pool)
521 .await?;
522 Ok(row.try_get("text")?)
523}
524
525async fn fetch_content_image(
526 pool: &Pool,
527 table: &str,
528 response_id: &str,
529 index: i64,
530 part_index: i64,
531) -> Result<ImageUrl, Error> {
532 let sql = format!(
533 "SELECT url, detail FROM {table} \
534 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
535 );
536 let row = sqlx::query(&sql)
537 .bind(response_id)
538 .bind(index)
539 .bind(part_index)
540 .fetch_one(&**pool)
541 .await?;
542 let url: String = row.try_get("url")?;
543 let detail_str: Option<String> = row.try_get("detail")?;
544 let detail = match detail_str {
545 Some(s) => serde_json::from_value(serde_json::Value::String(s))?,
546 None => None,
547 };
548 Ok(ImageUrl { url, detail })
549}
550
551async fn fetch_content_audio(
552 pool: &Pool,
553 table: &str,
554 response_id: &str,
555 index: i64,
556 part_index: i64,
557) -> Result<InputAudio, Error> {
558 let sql = format!(
559 "SELECT data, format FROM {table} \
560 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
561 );
562 let row = sqlx::query(&sql)
563 .bind(response_id)
564 .bind(index)
565 .bind(part_index)
566 .fetch_one(&**pool)
567 .await?;
568 Ok(InputAudio {
569 data: row.try_get("data")?,
570 format: row.try_get("format")?,
571 })
572}
573
574async fn fetch_content_video(
575 pool: &Pool,
576 table: &str,
577 response_id: &str,
578 index: i64,
579 part_index: i64,
580) -> Result<VideoUrl, Error> {
581 let sql = format!(
582 "SELECT url FROM {table} \
583 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
584 );
585 let row = sqlx::query(&sql)
586 .bind(response_id)
587 .bind(index)
588 .bind(part_index)
589 .fetch_one(&**pool)
590 .await?;
591 Ok(VideoUrl { url: row.try_get("url")? })
592}
593
594async fn fetch_content_file(
595 pool: &Pool,
596 table: &str,
597 response_id: &str,
598 index: i64,
599 part_index: i64,
600) -> Result<File, Error> {
601 let sql = format!(
602 "SELECT file_data, file_id, filename, file_url FROM {table} \
603 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
604 );
605 let row = sqlx::query(&sql)
606 .bind(response_id)
607 .bind(index)
608 .bind(part_index)
609 .fetch_one(&**pool)
610 .await?;
611 Ok(File {
612 file_data: row.try_get("file_data")?,
613 file_id: row.try_get("file_id")?,
614 filename: row.try_get("filename")?,
615 file_url: row.try_get("file_url")?,
616 })
617}
618
619fn require_row_index(
620 table_kind: MessageTable,
621 row_index: Option<i64>,
622) -> Result<i64, Error> {
623 row_index.ok_or_else(|| {
624 Error::InvalidData(format!(
625 "objectiveai.messages row for table {table_kind:?} is missing row_index"
626 ))
627 })
628}
629
630fn require_row_sub_index(
631 table_kind: MessageTable,
632 row_sub_index: Option<i64>,
633) -> Result<i64, Error> {
634 row_sub_index.ok_or_else(|| {
635 Error::InvalidData(format!(
636 "objectiveai.messages row for table {table_kind:?} is missing row_sub_index"
637 ))
638 })
639}
640
641fn require_full_indices(
642 table_kind: MessageTable,
643 row_index: Option<i64>,
644 row_sub_index: Option<i64>,
645) -> Result<(i64, i64), Error> {
646 Ok((
647 require_row_index(table_kind, row_index)?,
648 require_row_sub_index(table_kind, row_sub_index)?,
649 ))
650}