blob: c38c58b74d10b16e67e9c14e082a182af808959a [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::*;
use maka_agent_facts::artifact::ArtifactKind;
use maka_agent_facts::artifact::ArtifactSource;
use maka_agent_facts::attachment::AttachmentKind;
use maka_agent_facts::attachment::AttachmentRef;
use maka_agent_facts::attachment::StorageRef;
use maka_agent_facts::read::ReadInput;
impl Recall {
pub(super) async fn material(
self: &Arc<Self>,
call: Scope,
input: search::Material,
) -> Result<ToolSuccess, ToolError> {
self.check(&call).await?;
let target = call
.identity
.agent()
.ok_or_else(|| failed("RecallMaterial requires an Agent call"))?
.session_id
.clone();
self.session(&input.session_id).await.map_err(failed)?;
let mut ticket = call.resources.reserve()?;
ticket.start();
let recall = self.clone();
self.workers
.spawn(async move {
// The actual worker retains its call ticket until SQLite settles.
let result = async {
recall.check(&call).await?;
let record = recall
.store
.get_artifact(&input.session_id, &input.artifact_id)
.await
.map_err(failed)?
.record
.filter(|record| record.source == ArtifactSource::UserUpload)
.ok_or_else(|| failed("Historical user attachment unavailable"))?;
let mime_type = record
.mime_type
.clone()
.ok_or_else(|| failed("Attachment media type unavailable"))?;
let attachment = AttachmentRef {
kind: AttachmentKind::from_metadata(&mime_type, &record.name),
name: record.name.clone(),
mime_type,
bytes: record.size_bytes,
storage_ref: StorageRef::SessionFile {
session_id: input.session_id.clone(),
relative_path: input.artifact_id.clone(),
},
};
let namespace = maka_capabilities::storage::Namespace::new(
crate::IMPLEMENTATION,
maka_plugin_core::scope::Scope::Profile,
)
.map_err(failed)?;
let copied = recall
.store
.copy_plugin_attachment(
&namespace,
maka_agent_ports::execution::CopyAttachment {
attachment,
target_session_id: target.clone(),
},
)
.await
.map_err(|error| match error {
maka_event_log::StoreError::CommitUnknown(_) => {
ToolError::OutcomeUnknown(
"Recall attachment copy outcome is unknown".into(),
)
}
error => failed(error),
})?;
recall.check(&call).await?;
let StorageRef::SessionFile {
relative_path: id, ..
} = &copied.storage_ref
else {
unreachable!()
};
let request = ReadInput {
path: copied
.storage_ref
.resource_ref()
.ok_or_else(|| failed("Attachment address unavailable"))?,
offset: input.offset,
limit: input.limit,
}
.resolve()
.map_err(failed)?;
let output = maka_files_native::attachments::read(
&recall.store,
&target,
id,
&request,
record.kind == ArtifactKind::Image,
&call.cancellation,
)
.await?;
recall.check(&call).await?;
match output {
maka_files_native::Output::Json(value) => Ok(value.into()),
maka_files_native::Output::Image { bytes, mime_type } => {
ToolSuccess::image(bytes, mime_type).map_err(failed)
}
}
}
.await;
ticket.complete(Ok(()));
result
})
.await
.map_err(|_| {
ToolError::CleanupUnconfirmed("Recall attachment worker disappeared".into())
})?
}
}