Skip to main content

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}