blob: 6a03dd9d673ddd2ae8845d23ae0c2b5503e26f0e [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::EventRef;
use crate::RunError;
use maka_agent_facts::attachment::StorageRef;
use maka_agent_facts::event::Fact;
use maka_agent_facts::event::InvocationInput;
use maka_agent_facts::event::ToolOutcome;
use maka_agent_facts::tool_output::DurableToolProjection;
use maka_event_log::EventLog;
use std::collections::HashMap;
use tokio_util::sync::CancellationToken;
/// Request-local rendering aliases. Canonical references and evidence stay intact.
pub(super) struct Resources<'a> {
session: &'a str,
inherited: HashMap<(&'a str, &'a str), Option<StorageRef>>,
}
impl<'a> Resources<'a> {
pub fn native(session: &'a str) -> Self {
Self {
session,
inherited: HashMap::new(),
}
}
pub async fn load(
log: &EventLog,
session: &'a str,
events: impl Iterator<Item = EventRef<'a>>,
cancellation: &CancellationToken,
) -> Result<Self, RunError> {
let mut resources = Self::native(session);
for event in events.filter_map(EventRef::canonical) {
let mut references = Vec::new();
match &event.event.fact {
Fact::InvocationOpened {
input: InvocationInput::Message { content, .. },
..
} => {
references.extend(content.attachments.iter().flatten().map(|a| &a.storage_ref));
}
Fact::MessageSteered { message, .. } => {
references.extend(
message
.content
.attachments
.iter()
.flatten()
.map(|a| &a.storage_ref),
);
}
Fact::ToolSettled {
outcome:
ToolOutcome::Succeeded {
model_projection: DurableToolProjection::Content { parts },
..
},
..
} => {
references.extend(
parts
.iter()
.filter_map(|part| part.media().map(|(reference, _)| reference)),
);
}
_ => {}
}
for reference in references {
let StorageRef::SessionFile {
session_id,
relative_path,
} = reference
else {
continue;
};
if session_id == session
|| resources
.inherited
.contains_key(&(session_id.as_str(), relative_path.as_str()))
{
continue;
}
if cancellation.is_cancelled() {
return Err(RunError::Cancelled);
}
let resolved = log
.resolve_history_artifact(session, session_id, relative_path)
.await?;
resources
.inherited
.insert((session_id, relative_path), resolved);
}
}
Ok(resources)
}
pub fn resolve<'b>(&'b self, reference: &'b StorageRef) -> Option<&'b StorageRef> {
match reference {
StorageRef::SessionFile {
session_id,
relative_path,
} if session_id != self.session => self
.inherited
.get(&(session_id.as_str(), relative_path.as_str()))
.and_then(Option::as_ref),
_ => Some(reference),
}
}
}