Skip to main content

claw_core/store/
tool_output.rs

1//! Tool output store.
2//!
3//! The `tool_output` table records the results produced by agent tool calls.
4//! Each record captures the tool name, the raw output payload, and metadata
5//! needed to correlate the output with the originating session and agent.
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use sqlx::SqlitePool;
10use uuid::Uuid;
11
12use crate::error::{ClawError, ClawResult};
13
14/// A single tool-output record.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ToolOutputRecord {
17    /// Unique row identifier.
18    pub id: Uuid,
19    /// The session in which the tool was invoked.
20    pub session_id: String,
21    /// The name of the tool that produced this output.
22    pub tool_name: String,
23    /// Serialized JSON output payload.
24    pub output: serde_json::Value,
25    /// Whether the tool call succeeded.
26    pub success: bool,
27    /// Timestamp when the tool output was recorded.
28    pub created_at: DateTime<Utc>,
29}
30
31/// Data-access object for the `tool_output` table.
32#[derive(Debug)]
33pub struct ToolOutputStore<'a> {
34    pool: &'a SqlitePool,
35}
36
37impl<'a> ToolOutputStore<'a> {
38    /// Create a new store bound to `pool`.
39    pub fn new(pool: &'a SqlitePool) -> Self {
40        ToolOutputStore { pool }
41    }
42
43    /// Insert a new tool-output record.
44    ///
45    /// # Errors
46    ///
47    /// Returns a [`ClawError`] if the SQL execution fails.
48    pub async fn insert(&self, record: &ToolOutputRecord) -> ClawResult<()> {
49        sqlx::query(
50            r#"
51            INSERT INTO tool_output (id, session_id, tool_name, output, success, created_at)
52            VALUES (?, ?, ?, ?, ?, ?)
53            "#,
54        )
55        .bind(record.id.to_string())
56        .bind(&record.session_id)
57        .bind(&record.tool_name)
58        .bind(serde_json::to_string(&record.output)?)
59        .bind(record.success)
60        .bind(record.created_at.to_rfc3339())
61        .execute(self.pool)
62        .await?;
63
64        Ok(())
65    }
66
67    /// Fetch all tool-output records for a given `session_id`, ordered by
68    /// `created_at` ascending.
69    ///
70    /// # Errors
71    ///
72    /// Returns a [`ClawError`] if the query fails.
73    pub async fn get_by_session(&self, session_id: &str) -> ClawResult<Vec<ToolOutputRecord>> {
74        let rows = sqlx::query_as::<_, (String, String, String, String, bool, String)>(
75            "SELECT id, session_id, tool_name, output, success, created_at \
76             FROM tool_output WHERE session_id = ? ORDER BY created_at ASC",
77        )
78        .bind(session_id)
79        .fetch_all(self.pool)
80        .await?;
81
82        rows.into_iter()
83            .map(|(id, session_id, tool_name, output, success, created_at)| {
84                Ok(ToolOutputRecord {
85                    id: Uuid::parse_str(&id).map_err(|e| ClawError::Store(e.to_string()))?,
86                    session_id,
87                    tool_name,
88                    output: serde_json::from_str(&output)?,
89                    success,
90                    created_at: DateTime::parse_from_rfc3339(&created_at)
91                        .map_err(|e| ClawError::Store(e.to_string()))?
92                        .with_timezone(&Utc),
93                })
94            })
95            .collect()
96    }
97}