#[cfg(test)]
#[cfg(feature = "picker")]
pub(crate) mod fixture;
#[cfg(feature = "picker")]
pub(crate) mod index;
#[cfg(test)]
#[cfg(feature = "picker")]
mod tests;
#[cfg(feature = "picker")]
use std::sync::Arc;
#[cfg(feature = "picker")]
use serde::Serialize;
use crate::catalog::Catalog;
#[cfg(feature = "picker")]
const CANDIDATES: usize = 3;
#[cfg(feature = "picker")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct Candidate {
pub(crate) name: String,
pub(crate) description: String,
}
#[cfg(feature = "picker")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum Shortlist {
Candidates(Vec<Candidate>),
Unavailable,
Failed(String),
}
#[derive(Clone)]
#[non_exhaustive]
pub(crate) struct Retrieval {
#[cfg(feature = "picker")]
index: Option<Arc<index::Index>>,
}
impl Retrieval {
#[must_use]
pub(crate) fn idle() -> Retrieval {
Retrieval {
#[cfg(feature = "picker")]
index: None,
}
}
#[must_use]
pub(crate) fn start(catalog: &Catalog) -> Retrieval {
Retrieval::built(catalog)
}
#[cfg(feature = "picker")]
#[must_use]
pub(crate) fn shortlist(&self, capability: &str) -> Shortlist {
self.ranked(capability)
}
#[must_use]
pub(crate) fn rebuilt(&self, catalog: &Catalog) -> Reindex {
self.refreshed(catalog)
}
#[cfg(all(test, feature = "picker"))]
#[must_use]
pub(crate) fn reindexed(&self, catalog: &Catalog) -> Retrieval {
self.rebuilt(catalog).into_retrieval()
}
#[must_use]
pub(crate) fn is_available(&self) -> bool {
self.available()
}
#[cfg(feature = "picker")]
fn built(catalog: &Catalog) -> Retrieval {
match index::Index::build(catalog) {
Some(index) => {
tracing::info!("need_prompt ranks {} prompt(s)", index.len());
Retrieval {
index: Some(Arc::new(index)),
}
}
None => Retrieval::idle(),
}
}
#[cfg(not(feature = "picker"))]
fn built(_catalog: &Catalog) -> Retrieval {
tracing::info!("this build has no picker feature: need_prompt is not published");
Retrieval::idle()
}
#[cfg(feature = "picker")]
fn ranked(&self, capability: &str) -> Shortlist {
match &self.index {
Some(index) => index.shortlist(capability, CANDIDATES),
None => Shortlist::Unavailable,
}
}
#[cfg(feature = "picker")]
fn refreshed(&self, catalog: &Catalog) -> Reindex {
let Some(current) = &self.index else {
return Reindex::Unchanged(Retrieval::idle());
};
match current.rebuild(catalog) {
Some(rebuilt) => {
tracing::info!("need_prompt now ranks {} prompt(s)", rebuilt.len());
Reindex::Rebuilt(Retrieval {
index: Some(Arc::new(rebuilt)),
})
}
None => Reindex::Stale(self.clone()),
}
}
#[cfg(not(feature = "picker"))]
#[expect(
clippy::unused_self,
reason = "mirrors the picker-enabled method's signature so the caller needs no cfg"
)]
fn refreshed(&self, _catalog: &Catalog) -> Reindex {
Reindex::Unchanged(Retrieval::idle())
}
#[cfg(feature = "picker")]
fn available(&self) -> bool {
self.index.is_some()
}
#[cfg(not(feature = "picker"))]
#[expect(
clippy::unused_self,
reason = "mirrors the picker-enabled method's signature so the caller needs no cfg"
)]
fn available(&self) -> bool {
false
}
#[cfg(all(test, feature = "picker"))]
pub(crate) fn indexed_with(
model: &promptforge_tool_picker::Model,
catalog: &Catalog,
) -> Retrieval {
match index::Index::build_with(model, catalog) {
Some(index) => Retrieval {
index: Some(Arc::new(index)),
},
None => Retrieval::idle(),
}
}
#[cfg(all(test, feature = "picker"))]
pub(crate) fn same_index(&self, other: &Retrieval) -> bool {
match (&self.index, &other.index) {
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
(None, None) => true,
_ => false,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub(crate) enum Reindex {
#[cfg_attr(
not(feature = "picker"),
expect(
dead_code,
reason = "only a picker build has an index to rebuild; the arm still exists so reload needs no cfg"
)
)]
Rebuilt(Retrieval),
#[cfg_attr(
not(feature = "picker"),
expect(
dead_code,
reason = "only a picker build can fail a rebuild; the arm still exists so reload needs no cfg"
)
)]
Stale(Retrieval),
Unchanged(Retrieval),
}
impl Reindex {
#[must_use]
pub(crate) fn into_retrieval(self) -> Retrieval {
match self {
Reindex::Rebuilt(retrieval)
| Reindex::Stale(retrieval)
| Reindex::Unchanged(retrieval) => retrieval,
}
}
#[must_use]
pub(crate) fn is_stale(&self) -> bool {
matches!(self, Reindex::Stale(_))
}
}
const _: fn() = || {
fn assert_send_sync_static<T: Send + Sync + 'static>() {}
assert_send_sync_static::<Retrieval>();
};
impl Default for Retrieval {
fn default() -> Retrieval {
Retrieval::idle()
}
}
impl std::fmt::Debug for Retrieval {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Retrieval")
.field("available", &self.is_available())
.finish_non_exhaustive()
}
}