blob: 0994f83db8a0924a7ba33b0fc43f78582cd3fdb3 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::{StoreError, sequence_number};
use maka_presentation::{Content, Message, Row};
use rusqlite::Connection as RawConnection;
const COMPACTION: &str = r#"SELECT c.event_id, json_extract(c.event_json, '$.invocation.turn_id') AS turn_id,
catalog_time(json_extract(c.event_json, '$.recorded_at')) AS adopted_at,
(SELECT catalog_time(json_extract(r.event_json, '$.recorded_at'))
FROM runtime_events r
WHERE r.invocation_id = c.invocation_id AND r.kind = 'model_requested'
AND json_extract(r.event_json, '$.fact.purpose') = 'summary'
AND r.sequence <= adopted.sequence
AND json_extract(r.event_json, '$.fact.source_high_water') = json_extract(adopted.event_json, '$.fact.source_high_water')
AND json_extract(r.event_json, '$.fact.source_digest') = json_extract(adopted.event_json, '$.fact.source_digest')
AND NOT EXISTS (SELECT 1 FROM runtime_events boundary
WHERE boundary.invocation_id = c.invocation_id AND boundary.sequence > r.sequence AND boundary.sequence < c.sequence
AND boundary.kind = 'context_checkpoint_recorded')
ORDER BY r.sequence LIMIT 1) AS started_at
FROM runtime_events c JOIN runtime_events adopted
ON adopted.invocation_id = c.invocation_id AND adopted.kind = 'model_requested'
AND adopted.operation_id = json_extract(c.event_json, '$.fact.checkpoint.summary_step_id')
WHERE c.sequence = ? AND c.kind = 'context_checkpoint_recorded'"#;
/// Project adoption metadata without hydrating the private summary body.
pub(super) fn project(db: &RawConnection, sequence: i64) -> Result<Row, StoreError> {
use maka_agent_facts::history::RowPosition;
let (id, turn, adopted, started): (String, String, i64, i64) =
db.prepare_cached(COMPACTION)?
.query_row([sequence], |row| {
Ok((
row.get("event_id")?,
row.get("turn_id")?,
row.get("adopted_at")?,
row.get("started_at")?,
))
})?;
let ts = sequence_number(adopted)?;
let started = sequence_number(started)?;
Ok(Row {
position: RowPosition {
source_sequence: sequence_number(sequence)?,
item_index: 0,
},
message: Message {
id,
turn_id: turn,
ts,
content: Content::ContextCompacted {
duration_ms: ts.saturating_sub(started),
},
},
})
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use sqlx::{Connection, SqliteConnection};
use std::time::{Duration, UNIX_EPOCH};
#[tokio::test]
async fn concurrent_main_does_not_end_summary_timing_but_new_source_does() {
for new_source in [false, true] {
let mut db = SqliteConnection::connect(":memory:").await.unwrap();
crate::sqlite_functions::register(&mut db).await.unwrap();
sqlx::query("CREATE TABLE runtime_events(sequence INTEGER, invocation_id TEXT, kind TEXT, operation_id TEXT, event_id TEXT, event_json TEXT)").execute(&mut db).await.unwrap();
for (sequence, at, kind, step, fact) in [
(
1,
1000,
"model_requested",
"summary",
json!({"purpose":"summary","source_high_water":1,"source_digest":"source"}),
),
(
2,
1500,
"model_requested",
"main",
json!({"purpose":"main","source_high_water":2,"source_digest":"main"}),
),
(
3,
3000,
"model_requested",
"retry",
json!({"purpose":"summary","source_high_water":if new_source {2} else {1},"source_digest":if new_source {"new-source"} else {"source"}}),
),
(
4,
5500,
"context_checkpoint_recorded",
"",
json!({"checkpoint":{"summary_step_id":"retry"}}),
),
] {
let value = json!({"invocation":{"turn_id":"turn"}, "recorded_at":UNIX_EPOCH + Duration::from_millis(at), "fact":fact});
sqlx::query("INSERT INTO runtime_events VALUES (?, 'invocation', ?, ?, ?, ?)")
.bind(sequence)
.bind(kind)
.bind(step)
.bind(format!("event-{sequence}"))
.bind(value.to_string())
.execute(&mut db)
.await
.unwrap();
}
let row = db
.run_on_worker(|handle| {
// SAFETY: callback-local non-owning wrapper, no handle-backed result.
let borrowed = unsafe { RawConnection::from_handle(handle.as_ptr()) }?;
project(&borrowed, 4)
})
.await
.unwrap()
.unwrap();
assert_eq!(row.message.id, "event-4");
assert_eq!(row.message.ts, 5500);
assert_eq!(
row.message.content,
Content::ContextCompacted {
duration_ms: if new_source { 2500 } else { 4500 }
}
);
}
}
}