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: String = msg.try_get("response_id")?;
39 let table_kind: MessageTable = msg.try_get("table_kind")?;
40 let row_index: Option<i64> = msg.try_get("row_index")?;
41 let row_sub_index: Option<i64> = msg.try_get("row_sub_index")?;
42
43 Ok(Some(load_payload(
44 pool,
45 table_kind,
46 &response_id,
47 row_index,
48 row_sub_index,
49 ).await?))
50}
51
52async fn load_payload(
53 pool: &Pool,
54 table_kind: MessageTable,
55 response_id: &str,
56 row_index: Option<i64>,
57 row_sub_index: Option<i64>,
58) -> Result<Response, Error> {
59 match table_kind {
60 MessageTable::MessageQueueText => {
67 let id = require_row_index(table_kind, row_index)?;
68 let text: String = sqlx::query_scalar(
69 "SELECT text FROM objectiveai.message_queue_texts WHERE id = $1",
70 )
71 .bind(id)
72 .fetch_one(&**pool)
73 .await?;
74 Ok(Response::Text { text })
75 }
76 MessageTable::MessageQueueImage => {
77 let id = require_row_index(table_kind, row_index)?;
78 let row = sqlx::query(
79 "SELECT url, detail FROM objectiveai.message_queue_images WHERE id = $1",
80 )
81 .bind(id)
82 .fetch_one(&**pool)
83 .await?;
84 let url: String = row.try_get("url")?;
85 let detail_str: Option<String> = row.try_get("detail")?;
86 let detail = match detail_str {
87 Some(s) => serde_json::from_value(serde_json::Value::String(s))?,
88 None => None,
89 };
90 Ok(Response::Image(ImageUrl { url, detail }))
91 }
92 MessageTable::MessageQueueAudio => {
93 let id = require_row_index(table_kind, row_index)?;
94 let row = sqlx::query(
95 "SELECT data, format FROM objectiveai.message_queue_audios WHERE id = $1",
96 )
97 .bind(id)
98 .fetch_one(&**pool)
99 .await?;
100 Ok(Response::Audio(InputAudio {
101 data: row.try_get("data")?,
102 format: row.try_get("format")?,
103 }))
104 }
105 MessageTable::MessageQueueVideo => {
106 let id = require_row_index(table_kind, row_index)?;
107 let url: String = sqlx::query_scalar(
108 "SELECT url FROM objectiveai.message_queue_videos WHERE id = $1",
109 )
110 .bind(id)
111 .fetch_one(&**pool)
112 .await?;
113 Ok(Response::Video(VideoUrl { url }))
114 }
115 MessageTable::MessageQueueFile => {
116 let id = require_row_index(table_kind, row_index)?;
117 let row = sqlx::query(
118 "SELECT file_data, file_id, filename, file_url \
119 FROM objectiveai.message_queue_files WHERE id = $1",
120 )
121 .bind(id)
122 .fetch_one(&**pool)
123 .await?;
124 Ok(Response::File(File {
125 file_data: row.try_get("file_data")?,
126 file_id: row.try_get("file_id")?,
127 filename: row.try_get("filename")?,
128 file_url: row.try_get("file_url")?,
129 }))
130 }
131 MessageTable::AgentCompletionRequest => {
132 let (body, created_at, sender) =
133 fetch_request_blob(pool, "objectiveai.agent_completion_requests", response_id).await?;
134 let body: AgentCompletionCreateParams = serde_json::from_value(body)?;
135 Ok(Response::AgentCompletionRequest {
136 response_id: response_id.to_string(),
137 sender_agent_instance_hierarchy: sender,
138 body,
139 created_at,
140 })
141 }
142 MessageTable::VectorCompletionRequest => {
143 let (body, created_at, sender) =
144 fetch_request_blob(pool, "objectiveai.vector_completion_requests", response_id).await?;
145 let body: VectorCompletionCreateParams = serde_json::from_value(body)?;
146 Ok(Response::VectorCompletionRequest {
147 response_id: response_id.to_string(),
148 sender_agent_instance_hierarchy: sender,
149 body,
150 created_at,
151 })
152 }
153 MessageTable::FunctionExecutionRequest => {
154 let (body, created_at, sender) =
155 fetch_request_blob(pool, "objectiveai.function_execution_requests", response_id).await?;
156 let body: FunctionExecutionCreateParams = serde_json::from_value(body)?;
157 Ok(Response::FunctionExecutionRequest {
158 response_id: response_id.to_string(),
159 sender_agent_instance_hierarchy: sender,
160 body,
161 created_at,
162 })
163 }
164 MessageTable::AssistantResponseRefusal => {
165 let index = require_row_index(table_kind, row_index)?;
166 let text = fetch_indexed_text(
167 pool,
168 "objectiveai.assistant_response_refusal",
169 response_id,
170 index,
171 )
172 .await?;
173 Ok(Response::Text { text })
174 }
175 MessageTable::AssistantResponseReasoning => {
176 let index = require_row_index(table_kind, row_index)?;
177 let text = fetch_indexed_text(
178 pool,
179 "objectiveai.assistant_response_reasoning",
180 response_id,
181 index,
182 )
183 .await?;
184 Ok(Response::Text { text })
185 }
186 MessageTable::AssistantResponseToolCalls => {
193 let index = require_row_index(table_kind, row_index)?;
194 let tool_call_index = require_row_sub_index(table_kind, row_sub_index)?;
195 let row = sqlx::query(
196 "SELECT arguments \
197 FROM objectiveai.assistant_response_tool_calls \
198 WHERE response_id = $1 AND \"index\" = $2 AND tool_call_index = $3",
199 )
200 .bind(response_id)
201 .bind(index)
202 .bind(tool_call_index)
203 .fetch_one(&**pool)
204 .await?;
205 Ok(Response::Text { text: row.try_get("arguments")? })
206 }
207 MessageTable::AssistantResponseContentText => {
208 let (index, part_index) =
209 require_full_indices(table_kind, row_index, row_sub_index)?;
210 let text = fetch_content_text(
211 pool,
212 "objectiveai.assistant_response_content_text",
213 response_id,
214 index,
215 part_index,
216 )
217 .await?;
218 Ok(Response::Text { text })
219 }
220 MessageTable::AssistantResponseContentImage => {
221 let (index, part_index) =
222 require_full_indices(table_kind, row_index, row_sub_index)?;
223 let image_url = fetch_content_image(
224 pool,
225 "objectiveai.assistant_response_content_image",
226 response_id,
227 index,
228 part_index,
229 )
230 .await?;
231 Ok(Response::Image(image_url))
232 }
233 MessageTable::AssistantResponseContentAudio => {
234 let (index, part_index) =
235 require_full_indices(table_kind, row_index, row_sub_index)?;
236 let input_audio = fetch_content_audio(
237 pool,
238 "objectiveai.assistant_response_content_audio",
239 response_id,
240 index,
241 part_index,
242 )
243 .await?;
244 Ok(Response::Audio(input_audio))
245 }
246 MessageTable::AssistantResponseContentVideo => {
247 let (index, part_index) =
248 require_full_indices(table_kind, row_index, row_sub_index)?;
249 let video_url = fetch_content_video(
250 pool,
251 "objectiveai.assistant_response_content_video",
252 response_id,
253 index,
254 part_index,
255 )
256 .await?;
257 Ok(Response::Video(video_url))
258 }
259 MessageTable::AssistantResponseContentFile => {
260 let (index, part_index) =
261 require_full_indices(table_kind, row_index, row_sub_index)?;
262 let file = fetch_content_file(
263 pool,
264 "objectiveai.assistant_response_content_file",
265 response_id,
266 index,
267 part_index,
268 )
269 .await?;
270 Ok(Response::File(file))
271 }
272 MessageTable::ToolResponseContentText => {
273 let (index, part_index) =
274 require_full_indices(table_kind, row_index, row_sub_index)?;
275 let text = fetch_content_text(
276 pool,
277 "objectiveai.tool_response_content_text",
278 response_id,
279 index,
280 part_index,
281 )
282 .await?;
283 Ok(Response::Text { text })
284 }
285 MessageTable::ToolResponseContentImage => {
286 let (index, part_index) =
287 require_full_indices(table_kind, row_index, row_sub_index)?;
288 let image_url = fetch_content_image(
289 pool,
290 "objectiveai.tool_response_content_image",
291 response_id,
292 index,
293 part_index,
294 )
295 .await?;
296 Ok(Response::Image(image_url))
297 }
298 MessageTable::ToolResponseContentAudio => {
299 let (index, part_index) =
300 require_full_indices(table_kind, row_index, row_sub_index)?;
301 let input_audio = fetch_content_audio(
302 pool,
303 "objectiveai.tool_response_content_audio",
304 response_id,
305 index,
306 part_index,
307 )
308 .await?;
309 Ok(Response::Audio(input_audio))
310 }
311 MessageTable::ToolResponseContentVideo => {
312 let (index, part_index) =
313 require_full_indices(table_kind, row_index, row_sub_index)?;
314 let video_url = fetch_content_video(
315 pool,
316 "objectiveai.tool_response_content_video",
317 response_id,
318 index,
319 part_index,
320 )
321 .await?;
322 Ok(Response::Video(video_url))
323 }
324 MessageTable::ToolResponseContentFile => {
325 let (index, part_index) =
326 require_full_indices(table_kind, row_index, row_sub_index)?;
327 let file = fetch_content_file(
328 pool,
329 "objectiveai.tool_response_content_file",
330 response_id,
331 index,
332 part_index,
333 )
334 .await?;
335 Ok(Response::File(file))
336 }
337 }
338}
339
340async fn fetch_request_blob(
341 pool: &Pool,
342 table: &str,
343 response_id: &str,
344) -> Result<(serde_json::Value, i64, String), Error> {
345 let sql = format!(
346 "SELECT body, created_at, sender_agent_instance_hierarchy \
347 FROM {table} WHERE response_id = $1",
348 );
349 let row = sqlx::query(&sql)
350 .bind(response_id)
351 .fetch_one(&**pool)
352 .await?;
353 let body: sqlx::types::Json<serde_json::Value> = row.try_get("body")?;
354 let created_at: i64 = row.try_get("created_at")?;
355 let sender: String = row.try_get("sender_agent_instance_hierarchy")?;
356 Ok((body.0, created_at, sender))
357}
358
359async fn fetch_indexed_text(
360 pool: &Pool,
361 table: &str,
362 response_id: &str,
363 index: i64,
364) -> Result<String, Error> {
365 let sql = format!(
366 "SELECT text FROM {table} \
367 WHERE response_id = $1 AND \"index\" = $2",
368 );
369 let row = sqlx::query(&sql)
370 .bind(response_id)
371 .bind(index)
372 .fetch_one(&**pool)
373 .await?;
374 Ok(row.try_get("text")?)
375}
376
377async fn fetch_content_text(
378 pool: &Pool,
379 table: &str,
380 response_id: &str,
381 index: i64,
382 part_index: i64,
383) -> Result<String, Error> {
384 let sql = format!(
385 "SELECT text FROM {table} \
386 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
387 );
388 let row = sqlx::query(&sql)
389 .bind(response_id)
390 .bind(index)
391 .bind(part_index)
392 .fetch_one(&**pool)
393 .await?;
394 Ok(row.try_get("text")?)
395}
396
397async fn fetch_content_image(
398 pool: &Pool,
399 table: &str,
400 response_id: &str,
401 index: i64,
402 part_index: i64,
403) -> Result<ImageUrl, Error> {
404 let sql = format!(
405 "SELECT url, detail FROM {table} \
406 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
407 );
408 let row = sqlx::query(&sql)
409 .bind(response_id)
410 .bind(index)
411 .bind(part_index)
412 .fetch_one(&**pool)
413 .await?;
414 let url: String = row.try_get("url")?;
415 let detail_str: Option<String> = row.try_get("detail")?;
416 let detail = match detail_str {
417 Some(s) => serde_json::from_value(serde_json::Value::String(s))?,
418 None => None,
419 };
420 Ok(ImageUrl { url, detail })
421}
422
423async fn fetch_content_audio(
424 pool: &Pool,
425 table: &str,
426 response_id: &str,
427 index: i64,
428 part_index: i64,
429) -> Result<InputAudio, Error> {
430 let sql = format!(
431 "SELECT data, format FROM {table} \
432 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
433 );
434 let row = sqlx::query(&sql)
435 .bind(response_id)
436 .bind(index)
437 .bind(part_index)
438 .fetch_one(&**pool)
439 .await?;
440 Ok(InputAudio {
441 data: row.try_get("data")?,
442 format: row.try_get("format")?,
443 })
444}
445
446async fn fetch_content_video(
447 pool: &Pool,
448 table: &str,
449 response_id: &str,
450 index: i64,
451 part_index: i64,
452) -> Result<VideoUrl, Error> {
453 let sql = format!(
454 "SELECT url FROM {table} \
455 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
456 );
457 let row = sqlx::query(&sql)
458 .bind(response_id)
459 .bind(index)
460 .bind(part_index)
461 .fetch_one(&**pool)
462 .await?;
463 Ok(VideoUrl { url: row.try_get("url")? })
464}
465
466async fn fetch_content_file(
467 pool: &Pool,
468 table: &str,
469 response_id: &str,
470 index: i64,
471 part_index: i64,
472) -> Result<File, Error> {
473 let sql = format!(
474 "SELECT file_data, file_id, filename, file_url FROM {table} \
475 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
476 );
477 let row = sqlx::query(&sql)
478 .bind(response_id)
479 .bind(index)
480 .bind(part_index)
481 .fetch_one(&**pool)
482 .await?;
483 Ok(File {
484 file_data: row.try_get("file_data")?,
485 file_id: row.try_get("file_id")?,
486 filename: row.try_get("filename")?,
487 file_url: row.try_get("file_url")?,
488 })
489}
490
491fn require_row_index(
492 table_kind: MessageTable,
493 row_index: Option<i64>,
494) -> Result<i64, Error> {
495 row_index.ok_or_else(|| {
496 Error::InvalidData(format!(
497 "objectiveai.messages row for table {table_kind:?} is missing row_index"
498 ))
499 })
500}
501
502fn require_row_sub_index(
503 table_kind: MessageTable,
504 row_sub_index: Option<i64>,
505) -> Result<i64, Error> {
506 row_sub_index.ok_or_else(|| {
507 Error::InvalidData(format!(
508 "objectiveai.messages row for table {table_kind:?} is missing row_sub_index"
509 ))
510 })
511}
512
513fn require_full_indices(
514 table_kind: MessageTable,
515 row_index: Option<i64>,
516 row_sub_index: Option<i64>,
517) -> Result<(i64, i64), Error> {
518 Ok((
519 require_row_index(table_kind, row_index)?,
520 require_row_sub_index(table_kind, row_sub_index)?,
521 ))
522}