mod files;
mod images;
use std::path::PathBuf;
use std::sync::Arc;
use theway_contract::attachments::{AttachmentError, AttachmentStore};
use theway_contract::user_input::{
InputImagePart, InputInjectedPart, InputPart, InputSource, UserInput,
};
use theway_transport::commands::{skill_prompt_preamble, split_skill_prompt};
use theway_transport::wire::WirePromptImage;
pub use images::decode_prompt_images;
pub struct PromptAdmission {
store: Arc<dyn AttachmentStore>,
cwd: PathBuf,
}
impl PromptAdmission {
pub fn new(store: Arc<dyn AttachmentStore>, cwd: PathBuf) -> Self {
Self { store, cwd }
}
pub async fn admit(
&self,
text: &str,
images: &[WirePromptImage],
source: InputSource,
source_ref: Option<String>,
) -> Result<UserInput, String> {
let decoded = decode_prompt_images(images)?;
let mut parts = files::parts(self.store.as_ref(), &self.cwd, text).await?;
for (name, bytes, media_type) in decoded {
let digest = self.store.put(&bytes).map_err(store_error)?;
parts.push(InputPart::Image(InputImagePart {
name,
digest,
bytes: bytes.len() as u64,
media_type,
}));
}
let (record_text, skill_name) = match split_skill_prompt(text) {
Some((skill_name, user_text)) => (user_text, Some(skill_name)),
None => (text.to_string(), None),
};
let input = UserInput {
text: record_text,
parts,
source,
source_ref,
};
let Some(name) = skill_name else {
return Ok(input);
};
let preamble = skill_prompt_preamble(&name);
let part = InputInjectedPart {
source: "skill".to_string(),
name: Some(name),
text: preamble,
};
Ok(Self::with_injected(input, part))
}
pub fn with_injected(mut input: UserInput, part: InputInjectedPart) -> UserInput {
input.parts.push(InputPart::Injected(part));
input
}
}
fn store_error(error: AttachmentError) -> String {
error.to_string()
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("attachments");