blob: 379ec48c08818fb00c26355d25e2bf19d49164b3 [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 super::{ContextEvent, invalid, selection::Selection};
use crate::{StoreError, sequence_number};
use maka_agent_facts::event::StoredEvent;
use sqlx::{Row, SqliteConnection};
pub(super) async fn read(
connection: &mut SqliteConnection,
selection: &Selection,
after: u64,
through: u64,
before: u64,
max_events: usize,
max_bytes: usize,
) -> Result<Vec<ContextEvent>, StoreError> {
let session = selection
.session
.as_deref()
.ok_or_else(|| invalid("context requires a Session"))?;
let filter = Selection::predicate("t", "?5");
let archive_filter = Selection::archive_predicate("t", "a", "?4", "?5");
let items = "t.kind IN ('model_completed','model_interrupted') AND EXISTS(SELECT 1 FROM runtime_events r
WHERE r.invocation_id=t.invocation_id AND r.operation_id=t.operation_id AND r.kind='model_requested'
AND json_extract(r.event_json,'$.fact.item_acceptance')=1)";
macro_rules! selected { ($prefix:literal, $suffix:literal) => { concat!($prefix,
" FROM session_history_events t LEFT JOIN runtime_events a ON a.kind='tool_result_archived'
AND CAST(json_extract(a.event_json,'$.fact.placeholder.identity.runtime_event_id') AS TEXT)=t.event_id AND {archive_filter}
WHERE t.sequence > ?1 AND t.sequence <= ?2 AND t.owner_session_id=?3
AND {filter} AND t.kind NOT IN ('model_observed','context_checkpoint_recorded','tool_result_archived')
AND NOT EXISTS(SELECT 1 FROM runtime_events o WHERE o.invocation_id=t.invocation_id
AND o.kind='invocation_opened' AND json_extract(o.event_json,'$.fact.input.kind')='context_compact')
AND NOT EXISTS(SELECT 1 FROM runtime_events r WHERE r.invocation_id=t.invocation_id
AND r.kind='model_requested' AND r.operation_id=t.operation_id AND json_extract(r.event_json,'$.fact.purpose')='summary')",
$suffix) }; }
let (count, bytes): (i64,i64) = sqlx::query_as(sqlx::AssertSqlSafe(format!(selected!(
"SELECT COUNT(*),COALESCE(SUM(CASE WHEN a.sequence IS NULL AND NOT ({items}) THEN length(CAST(t.event_json AS BLOB)) ELSE 0 END),0)", ""
), filter=filter, archive_filter=archive_filter, items=items))).bind(after as i64).bind(through as i64).bind(session).bind(before as i64).bind(&selection.lineage).fetch_one(&mut *connection).await?;
if sequence_number(count)? > max_events as u64 || sequence_number(bytes)? > max_bytes as u64 {
return Err(StoreError::PrefixTooLarge);
}
// Archived base bodies are NULL here, never retained with the returned tail.
let rows = sqlx::query(sqlx::AssertSqlSafe(format!(selected!(
"SELECT t.sequence,t.event_id,json_extract(a.event_json,'$.invocation.session_id') AS archive_writer,
a.sequence + 1 AS archives_before,
CASE WHEN a.sequence IS NULL AND NOT ({items}) THEN t.event_json END AS canonical,
CASE WHEN {items} THEN t.operation_id END AS model_step,
CASE WHEN {items} THEN json_extract(t.event_json,'$.invocation') END AS model_invocation,
(SELECT length(payload) FROM tool_result_payloads WHERE event_id=t.event_id) AS raw_bytes", " ORDER BY t.sequence"
), filter=filter, archive_filter=archive_filter, items=items))).bind(after as i64).bind(through as i64).bind(session).bind(before as i64).bind(&selection.lineage).fetch_all(&mut *connection).await?;
let mut used = bytes as usize;
let mut result = Vec::with_capacity(count as usize);
for row in rows {
if let Some(step) = row.try_get::<Option<String>, _>("model_step")? {
let projected = crate::model_items::project(
connection,
serde_json::from_str(row.try_get("model_invocation")?)?,
step,
sequence_number(row.try_get("sequence")?)?,
row.try_get("event_id")?,
max_bytes,
)
.await?;
used = used
.checked_add(serde_json::to_vec(&projected)?.len())
.ok_or(StoreError::PrefixTooLarge)?;
if used > max_bytes {
return Err(StoreError::PrefixTooLarge);
}
result.push(ContextEvent::ModelItems(Box::new(projected)));
} else if let Some(json) = row.try_get::<Option<&str>, _>("canonical")? {
let event = serde_json::from_str(json)?;
crate::tool_payloads::verify_binding(&event, row.try_get("raw_bytes")?)?;
result.push(ContextEvent::Canonical(Box::new(StoredEvent {
sequence: sequence_number(row.try_get("sequence")?)?,
event,
})));
} else {
let archived = crate::archive::archived(
connection,
row.try_get("archive_writer")?,
row.try_get("event_id")?,
sequence_number(row.try_get("archives_before")?)?,
)
.await?
.ok_or_else(|| invalid("missing accepted archive replacement"))?;
let bytes = serde_json::to_vec(&(
&archived.event_id,
&archived.invocation,
&archived.operation_id,
&archived.replacement,
archived.is_error,
))?
.len();
used = used.checked_add(bytes).ok_or(StoreError::PrefixTooLarge)?;
if used > max_bytes {
return Err(StoreError::PrefixTooLarge);
}
result.push(ContextEvent::Archived(Box::new(archived)));
}
}
Ok(result)
}