pub trait PromptSource:
Send
+ Sync
+ Debug {
// Required methods
fn load<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
name: &'life1 PromptName,
selector: &'life2 PromptSelector,
) -> Pin<Box<dyn Future<Output = Result<LoadedPrompt, PromptError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn describe(&self) -> &'static str;
}Expand description
Where prompt text comes from.
§The obligation
An implementation owes exactly one thing beyond returning text:
The text it returns must be the text whose hash the reference carries.
Everything downstream rests on that. The replay record stores the reference
and not the text, so an auditor answering “which prompt produced this turn”
is trusting the digest; a source that returns one text and a reference to
another turns the whole record into decoration. Returning
LoadedPrompt::new discharges the obligation by construction, and
LoadedPrompt::from_parts checks it when a reference arrives from
somewhere else.
Two smaller expectations follow from it:
- A source must not rewrite, trim or template-expand the text after hashing it. Normalize first, hash second.
- A pinned
PromptSelector::Versionthat the source cannot honour isPromptError::VersionNotFound, never a quiet substitution.
§Object safety
The trait is dyn-compatible through async_trait, because the runtime
holds one as Arc<dyn PromptSource> and because a cache decorates an
arbitrary one. Implementations are Send + Sync for the same reason.
use std::sync::Arc;
use turnframe_core::prompt::{
LoadedPrompt, PromptError, PromptName, PromptSelector, PromptSource,
};
#[derive(Debug)]
struct OnePrompt(&'static str);
#[async_trait::async_trait]
impl PromptSource for OnePrompt {
async fn load(
&self,
name: &PromptName,
_selector: &PromptSelector,
) -> Result<LoadedPrompt, PromptError> {
Ok(LoadedPrompt::new(name.clone(), "v1", self.0))
}
fn describe(&self) -> &'static str {
"one-prompt"
}
}
// The coercion is the assertion: a source is usable behind a shared
// pointer, which is how the runtime holds one.
let shared: Arc<dyn PromptSource> = Arc::new(OnePrompt("Say hello."));
assert_eq!(shared.describe(), "one-prompt");
let name = PromptName::from("greeting");
let loading = shared.load(&name, &PromptSelector::Latest);
// Awaiting it needs an executor; that the future exists at all is what
// dyn-compatibility buys.
drop(loading);Required Methods§
Sourcefn load<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
name: &'life1 PromptName,
selector: &'life2 PromptSelector,
) -> Pin<Box<dyn Future<Output = Result<LoadedPrompt, PromptError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn load<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
name: &'life1 PromptName,
selector: &'life2 PromptSelector,
) -> Pin<Box<dyn Future<Output = Result<LoadedPrompt, PromptError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Loads one prompt.
§Errors
See PromptError. A source that reaches a network reports a failure
to reach it as PromptError::Transport, so a caller can tell “this
prompt does not exist” from “this registry is down”.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".