objectiveai_cli/db/logs/errors.rs
1//! Logged failures — the `error` log kind.
2//!
3//! Errors are written by this dedicated helper, NOT the chunk-driven
4//! [`writer`](super::writer) (no dedup/update semantics — an error is
5//! one immutable row). One CTE round-trip inserts the
6//! `objectiveai.errors` content row and its `objectiveai.messages`
7//! event row; the content row's BIGSERIAL `id` doubles as the
8//! messages `row_index`, so identity never collides even when
9//! `response_id` is NULL (the post-lock pre-stream case — the only
10//! kind allowed a NULL response_id).
11
12use crate::db::{Error, Pool};
13
14use super::row::MessageTable;
15
16/// Persist one error into an agent's history. `response_id` is the
17/// response the failure belongs to when one existed, `None` for
18/// post-lock pre-stream failures. `error` is the CLI's user-facing
19/// error value (`Error::output_message()`). Returns the
20/// `objectiveai.errors.id` — the row's `messages.row_index`.
21pub async fn insert_error(
22 pool: &Pool,
23 agent_instance_hierarchy: &str,
24 response_id: Option<&str>,
25 error: &serde_json::Value,
26 timestamp: i64,
27) -> Result<i64, Error> {
28 let (id,): (i64,) = sqlx::query_as(
29 "WITH data_ins AS (\
30 INSERT INTO objectiveai.errors \
31 (agent_instance_hierarchy, response_id, error) \
32 VALUES ($1, $2, $3) RETURNING id\
33 ), msg_ins AS (\
34 INSERT INTO objectiveai.messages \
35 (response_id, \"table\", row_index, row_sub_index, \
36 agent_instance_hierarchy, \"timestamp\") \
37 SELECT $2, $4, id, NULL, $1, $5 FROM data_ins\
38 ) \
39 SELECT id FROM data_ins",
40 )
41 .bind(agent_instance_hierarchy)
42 .bind(response_id)
43 .bind(error)
44 .bind(MessageTable::Error)
45 .bind(timestamp)
46 .fetch_one(&**pool)
47 .await?;
48 Ok(id)
49}