Skip to main content

ferrin_core/
skills.rs

1//! Provider skill storage: [`upload_skill`].
2//!
3//! Design: `docs/01-architecture/11-other-modalities.md` ยง7.
4
5use std::future::IntoFuture;
6
7use ferrin_spec::BoxFuture;
8use ferrin_spec::SkillsRef;
9pub use ferrin_spec::skills::SkillFile;
10pub use ferrin_spec::skills::SkillFileData;
11use ferrin_spec::skills::UploadSkillOptions;
12pub use ferrin_spec::skills::UploadSkillResult;
13use tracing::Instrument;
14
15use crate::error::Error;
16use crate::modality::ModalityOptions;
17use crate::modality::impl_modality_builder;
18use crate::telemetry::ModelIdentity;
19use crate::telemetry::spans;
20
21/// Uploads a skill (a bundle of files) to the provider.
22#[must_use]
23pub fn upload_skill(skills: impl Into<SkillsRef>, files: Vec<SkillFile>) -> UploadSkill {
24    UploadSkill {
25        skills: skills.into(),
26        files,
27        display_title: None,
28        base: ModalityOptions::default(),
29    }
30}
31
32/// Builder returned by [`upload_skill`]; `.await` runs the upload.
33#[derive(Debug)]
34pub struct UploadSkill {
35    skills: SkillsRef,
36    files: Vec<SkillFile>,
37    display_title: Option<String>,
38    base: ModalityOptions,
39}
40
41impl UploadSkill {
42    /// Sets the display title.
43    #[must_use]
44    pub fn display_title(mut self, display_title: impl Into<String>) -> Self {
45        self.display_title = Some(display_title.into());
46        self
47    }
48}
49
50impl_modality_builder!(@no_retry UploadSkill);
51
52impl IntoFuture for UploadSkill {
53    type Output = Result<UploadSkillResult, Error>;
54    type IntoFuture = BoxFuture<'static, Self::Output>;
55
56    fn into_future(self) -> Self::IntoFuture {
57        Box::pin(async move {
58            let identity = ModelIdentity::new(self.skills.provider().clone(), "skills");
59            let span = spans::modality_span("upload_skill", &identity);
60            let skills = self.skills.clone();
61            let base = self.base.clone();
62            let files = self.files;
63            let display_title = self.display_title;
64            base.run(|base, token| {
65                async move {
66                    let result = skills
67                        .upload_skill(UploadSkillOptions {
68                            files,
69                            display_title,
70                            headers: base.request_headers(),
71                            provider_options: base.provider_options.clone(),
72                            cancellation: token,
73                        })
74                        .await
75                        .map_err(Error::from)?;
76                    spans::log_warnings(&result.warnings, &identity);
77                    Ok(result)
78                }
79                .instrument(span)
80            })
81            .await
82        })
83    }
84}