1use objectiveai_sdk::agent::completions::message::{File, ImageUrl, InputAudio, VideoUrl};
13use objectiveai_sdk::agent::completions::request::AgentCompletionCreateParams;
14use objectiveai_sdk::cli::command::agents::logs::read::id::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::ToolResponse => {
165 let index = require_row_index(table_kind, row_index)?;
166 let row = sqlx::query(
167 "SELECT tool_call_id FROM objectiveai.tool_response \
168 WHERE response_id = $1 AND \"index\" = $2",
169 )
170 .bind(response_id)
171 .bind(index)
172 .fetch_one(&**pool)
173 .await?;
174 Ok(Response::ToolResponse {
175 response_id: response_id.to_string(),
176 index,
177 tool_call_id: row.try_get("tool_call_id")?,
178 })
179 }
180 MessageTable::AssistantResponseRefusal => {
181 let index = require_row_index(table_kind, row_index)?;
182 let text = fetch_indexed_text(
183 pool,
184 "objectiveai.assistant_response_refusal",
185 response_id,
186 index,
187 )
188 .await?;
189 Ok(Response::Text { text })
190 }
191 MessageTable::AssistantResponseReasoning => {
192 let index = require_row_index(table_kind, row_index)?;
193 let text = fetch_indexed_text(
194 pool,
195 "objectiveai.assistant_response_reasoning",
196 response_id,
197 index,
198 )
199 .await?;
200 Ok(Response::Text { text })
201 }
202 MessageTable::AssistantResponseToolCalls => {
203 let index = require_row_index(table_kind, row_index)?;
204 let tool_call_index = require_row_sub_index(table_kind, row_sub_index)?;
205 let row = sqlx::query(
206 "SELECT tool_call_id, function_name, arguments \
207 FROM objectiveai.assistant_response_tool_calls \
208 WHERE response_id = $1 AND \"index\" = $2 AND tool_call_index = $3",
209 )
210 .bind(response_id)
211 .bind(index)
212 .bind(tool_call_index)
213 .fetch_one(&**pool)
214 .await?;
215 Ok(Response::ToolCall {
216 response_id: response_id.to_string(),
217 index,
218 tool_call_index,
219 tool_call_id: row.try_get("tool_call_id")?,
220 function_name: row.try_get("function_name")?,
221 arguments: row.try_get("arguments")?,
222 })
223 }
224 MessageTable::AssistantResponseContentText => {
225 let (index, part_index) =
226 require_full_indices(table_kind, row_index, row_sub_index)?;
227 let text = fetch_content_text(
228 pool,
229 "objectiveai.assistant_response_content_text",
230 response_id,
231 index,
232 part_index,
233 )
234 .await?;
235 Ok(Response::Text { text })
236 }
237 MessageTable::AssistantResponseContentImage => {
238 let (index, part_index) =
239 require_full_indices(table_kind, row_index, row_sub_index)?;
240 let image_url = fetch_content_image(
241 pool,
242 "objectiveai.assistant_response_content_image",
243 response_id,
244 index,
245 part_index,
246 )
247 .await?;
248 Ok(Response::Image(image_url))
249 }
250 MessageTable::AssistantResponseContentAudio => {
251 let (index, part_index) =
252 require_full_indices(table_kind, row_index, row_sub_index)?;
253 let input_audio = fetch_content_audio(
254 pool,
255 "objectiveai.assistant_response_content_audio",
256 response_id,
257 index,
258 part_index,
259 )
260 .await?;
261 Ok(Response::Audio(input_audio))
262 }
263 MessageTable::AssistantResponseContentVideo => {
264 let (index, part_index) =
265 require_full_indices(table_kind, row_index, row_sub_index)?;
266 let video_url = fetch_content_video(
267 pool,
268 "objectiveai.assistant_response_content_video",
269 response_id,
270 index,
271 part_index,
272 )
273 .await?;
274 Ok(Response::Video(video_url))
275 }
276 MessageTable::AssistantResponseContentFile => {
277 let (index, part_index) =
278 require_full_indices(table_kind, row_index, row_sub_index)?;
279 let file = fetch_content_file(
280 pool,
281 "objectiveai.assistant_response_content_file",
282 response_id,
283 index,
284 part_index,
285 )
286 .await?;
287 Ok(Response::File(file))
288 }
289 MessageTable::ToolResponseContentText => {
290 let (index, part_index) =
291 require_full_indices(table_kind, row_index, row_sub_index)?;
292 let text = fetch_content_text(
293 pool,
294 "objectiveai.tool_response_content_text",
295 response_id,
296 index,
297 part_index,
298 )
299 .await?;
300 Ok(Response::Text { text })
301 }
302 MessageTable::ToolResponseContentImage => {
303 let (index, part_index) =
304 require_full_indices(table_kind, row_index, row_sub_index)?;
305 let image_url = fetch_content_image(
306 pool,
307 "objectiveai.tool_response_content_image",
308 response_id,
309 index,
310 part_index,
311 )
312 .await?;
313 Ok(Response::Image(image_url))
314 }
315 MessageTable::ToolResponseContentAudio => {
316 let (index, part_index) =
317 require_full_indices(table_kind, row_index, row_sub_index)?;
318 let input_audio = fetch_content_audio(
319 pool,
320 "objectiveai.tool_response_content_audio",
321 response_id,
322 index,
323 part_index,
324 )
325 .await?;
326 Ok(Response::Audio(input_audio))
327 }
328 MessageTable::ToolResponseContentVideo => {
329 let (index, part_index) =
330 require_full_indices(table_kind, row_index, row_sub_index)?;
331 let video_url = fetch_content_video(
332 pool,
333 "objectiveai.tool_response_content_video",
334 response_id,
335 index,
336 part_index,
337 )
338 .await?;
339 Ok(Response::Video(video_url))
340 }
341 MessageTable::ToolResponseContentFile => {
342 let (index, part_index) =
343 require_full_indices(table_kind, row_index, row_sub_index)?;
344 let file = fetch_content_file(
345 pool,
346 "objectiveai.tool_response_content_file",
347 response_id,
348 index,
349 part_index,
350 )
351 .await?;
352 Ok(Response::File(file))
353 }
354 }
355}
356
357async fn fetch_request_blob(
358 pool: &Pool,
359 table: &str,
360 response_id: &str,
361) -> Result<(serde_json::Value, i64, String), Error> {
362 let sql = format!(
363 "SELECT body, created_at, sender_agent_instance_hierarchy \
364 FROM {table} WHERE response_id = $1",
365 );
366 let row = sqlx::query(&sql)
367 .bind(response_id)
368 .fetch_one(&**pool)
369 .await?;
370 let body: sqlx::types::Json<serde_json::Value> = row.try_get("body")?;
371 let created_at: i64 = row.try_get("created_at")?;
372 let sender: String = row.try_get("sender_agent_instance_hierarchy")?;
373 Ok((body.0, created_at, sender))
374}
375
376async fn fetch_indexed_text(
377 pool: &Pool,
378 table: &str,
379 response_id: &str,
380 index: i64,
381) -> Result<String, Error> {
382 let sql = format!(
383 "SELECT text FROM {table} \
384 WHERE response_id = $1 AND \"index\" = $2",
385 );
386 let row = sqlx::query(&sql)
387 .bind(response_id)
388 .bind(index)
389 .fetch_one(&**pool)
390 .await?;
391 Ok(row.try_get("text")?)
392}
393
394async fn fetch_content_text(
395 pool: &Pool,
396 table: &str,
397 response_id: &str,
398 index: i64,
399 part_index: i64,
400) -> Result<String, Error> {
401 let sql = format!(
402 "SELECT text FROM {table} \
403 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
404 );
405 let row = sqlx::query(&sql)
406 .bind(response_id)
407 .bind(index)
408 .bind(part_index)
409 .fetch_one(&**pool)
410 .await?;
411 Ok(row.try_get("text")?)
412}
413
414async fn fetch_content_image(
415 pool: &Pool,
416 table: &str,
417 response_id: &str,
418 index: i64,
419 part_index: i64,
420) -> Result<ImageUrl, Error> {
421 let sql = format!(
422 "SELECT url, detail FROM {table} \
423 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
424 );
425 let row = sqlx::query(&sql)
426 .bind(response_id)
427 .bind(index)
428 .bind(part_index)
429 .fetch_one(&**pool)
430 .await?;
431 let url: String = row.try_get("url")?;
432 let detail_str: Option<String> = row.try_get("detail")?;
433 let detail = match detail_str {
434 Some(s) => serde_json::from_value(serde_json::Value::String(s))?,
435 None => None,
436 };
437 Ok(ImageUrl { url, detail })
438}
439
440async fn fetch_content_audio(
441 pool: &Pool,
442 table: &str,
443 response_id: &str,
444 index: i64,
445 part_index: i64,
446) -> Result<InputAudio, Error> {
447 let sql = format!(
448 "SELECT data, format FROM {table} \
449 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
450 );
451 let row = sqlx::query(&sql)
452 .bind(response_id)
453 .bind(index)
454 .bind(part_index)
455 .fetch_one(&**pool)
456 .await?;
457 Ok(InputAudio {
458 data: row.try_get("data")?,
459 format: row.try_get("format")?,
460 })
461}
462
463async fn fetch_content_video(
464 pool: &Pool,
465 table: &str,
466 response_id: &str,
467 index: i64,
468 part_index: i64,
469) -> Result<VideoUrl, Error> {
470 let sql = format!(
471 "SELECT url FROM {table} \
472 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
473 );
474 let row = sqlx::query(&sql)
475 .bind(response_id)
476 .bind(index)
477 .bind(part_index)
478 .fetch_one(&**pool)
479 .await?;
480 Ok(VideoUrl { url: row.try_get("url")? })
481}
482
483async fn fetch_content_file(
484 pool: &Pool,
485 table: &str,
486 response_id: &str,
487 index: i64,
488 part_index: i64,
489) -> Result<File, Error> {
490 let sql = format!(
491 "SELECT file_data, file_id, filename, file_url FROM {table} \
492 WHERE response_id = $1 AND \"index\" = $2 AND part_index = $3",
493 );
494 let row = sqlx::query(&sql)
495 .bind(response_id)
496 .bind(index)
497 .bind(part_index)
498 .fetch_one(&**pool)
499 .await?;
500 Ok(File {
501 file_data: row.try_get("file_data")?,
502 file_id: row.try_get("file_id")?,
503 filename: row.try_get("filename")?,
504 file_url: row.try_get("file_url")?,
505 })
506}
507
508fn require_row_index(
509 table_kind: MessageTable,
510 row_index: Option<i64>,
511) -> Result<i64, Error> {
512 row_index.ok_or_else(|| {
513 Error::InvalidData(format!(
514 "objectiveai.messages row for table {table_kind:?} is missing row_index"
515 ))
516 })
517}
518
519fn require_row_sub_index(
520 table_kind: MessageTable,
521 row_sub_index: Option<i64>,
522) -> Result<i64, Error> {
523 row_sub_index.ok_or_else(|| {
524 Error::InvalidData(format!(
525 "objectiveai.messages row for table {table_kind:?} is missing row_sub_index"
526 ))
527 })
528}
529
530fn require_full_indices(
531 table_kind: MessageTable,
532 row_index: Option<i64>,
533 row_sub_index: Option<i64>,
534) -> Result<(i64, i64), Error> {
535 Ok((
536 require_row_index(table_kind, row_index)?,
537 require_row_sub_index(table_kind, row_sub_index)?,
538 ))
539}