use perspt_sdk::error::Result;
use perspt_sdk::prompt::{
CompiledPromptProgram, ManifestEntry, ModelDialect, PromptManifest, PromptMessageRole,
PromptRoute, PromptSectionId, PromptSectionVersion, PromptStage, RenderedSection,
SectionTemplate, StageComposition,
};
mod generated {
include!(concat!(env!("OUT_DIR"), "/prompt_sections.rs"));
}
pub use generated::{
adjudicate, evidence_summarize, graph_plan, repository_explore, session_bootstrap,
};
#[derive(Debug, Clone, Default)]
pub struct SectionOverrides {
map: std::collections::BTreeMap<String, perspt_sdk::prompt::SectionTemplate>,
}
impl SectionOverrides {
pub fn insert_replacement(&mut self, id: &str, body: &str) -> Result<()> {
let base = compiled_platform_templates()
.into_iter()
.map(|(_, template)| template)
.find(|template| template.schema.id.0 == id)
.ok_or_else(|| {
perspt_sdk::error::SdkError::Domain(format!(
"section {id:?} is not a compiled platform section; live \
substitution covers the five platform stages only"
))
})?;
let template = perspt_sdk::prompt::SectionTemplate {
schema: base.schema,
content_hash: perspt_sdk::prompt::SectionTemplate::hash_body(body),
body: body.to_string(),
};
self.map.insert(id.to_string(), template);
Ok(())
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn provenance(&self) -> Vec<(String, String)> {
self.map
.iter()
.map(|(id, template)| (id.clone(), template.content_hash.clone()))
.collect()
}
fn rendered(
&self,
base: perspt_sdk::prompt::SectionTemplate,
values: &std::collections::BTreeMap<String, perspt_sdk::prompt::VarValue>,
) -> Result<RenderedSection> {
match self.map.get(base.schema.id.0.as_str()) {
Some(replacement) => replacement.render(values),
None => base.render(values),
}
}
}
pub fn compiled_platform_templates() -> Vec<(&'static str, SectionTemplate)> {
let stages: [(&'static str, Vec<SectionTemplate>); 5] = [
("adjudicate", adjudicate::templates()),
("evidence_summarize", evidence_summarize::templates()),
("graph_plan", graph_plan::templates()),
("repository_explore", repository_explore::templates()),
("session_bootstrap", session_bootstrap::templates()),
];
let mut templates = Vec::new();
for (stage, list) in stages {
for template in list {
templates.push((stage, template));
}
}
templates
}
#[derive(Debug, Clone)]
pub struct PlatformStage {
pub stage: PromptStage,
pub separator: &'static str,
pub sections: Vec<RenderedSection>,
}
impl PlatformStage {
pub fn compile(
&self,
route: &PromptRoute,
dialect: &ModelDialect,
tool_spec_hash: &str,
) -> Result<CompiledPromptProgram> {
let mut composition = StageComposition::new(self.stage).system_separator(self.separator);
for section in &self.sections {
composition =
composition.resolved(section.clone(), perspt_sdk::prompt::OverrideOrigin::Base);
}
composition.compile(route, dialect, tool_spec_hash)
}
}
pub fn compile_invocation(
stage: &PlatformStage,
domain_sections: &[RenderedSection],
route: &PromptRoute,
dialect: &ModelDialect,
tool_spec_hash: &str,
) -> Result<perspt_sdk::prompt::CompiledPromptInvocation> {
let platform = stage.compile(route, dialect, tool_spec_hash)?;
let mut composition = StageComposition::new(stage.stage);
for section in domain_sections {
composition =
composition.resolved(section.clone(), perspt_sdk::prompt::OverrideOrigin::Base);
}
let domain = composition.compile(route, dialect, tool_spec_hash)?;
Ok(perspt_sdk::prompt::CompiledPromptInvocation::new(
platform, domain,
))
}
#[derive(Debug)]
pub struct PlatformPromptLibrary;
impl PlatformPromptLibrary {
pub fn session_bootstrap(
domain_id: &str,
overrides: &SectionOverrides,
) -> Result<PlatformStage> {
let role = session_bootstrap::Role {
domain_id: perspt_sdk::prompt::BoundedText::new(domain_id)?,
};
Ok(PlatformStage {
stage: PromptStage::SessionBootstrap,
separator: session_bootstrap::SEPARATOR,
sections: vec![
overrides.rendered(session_bootstrap::Role::template(), &role.values())?,
overrides.rendered(
session_bootstrap::Governance::template(),
&session_bootstrap::Governance {}.values(),
)?,
],
})
}
pub fn graph_plan(revision_shape: &str, overrides: &SectionOverrides) -> Result<PlatformStage> {
let protocol = graph_plan::UpdateProtocol {
revision_shape: perspt_sdk::prompt::BoundedText::new(revision_shape)?,
};
Ok(PlatformStage {
stage: PromptStage::GraphPlan,
separator: graph_plan::SEPARATOR,
sections: vec![
overrides.rendered(graph_plan::Role::template(), &graph_plan::Role {}.values())?,
overrides.rendered(graph_plan::UpdateProtocol::template(), &protocol.values())?,
],
})
}
pub fn repository_explore(overrides: &SectionOverrides) -> Result<PlatformStage> {
Ok(PlatformStage {
stage: PromptStage::RepositoryExplore,
separator: repository_explore::SEPARATOR,
sections: vec![
overrides.rendered(
repository_explore::Role::template(),
&repository_explore::Role {}.values(),
)?,
overrides.rendered(
repository_explore::Protocol::template(),
&repository_explore::Protocol {}.values(),
)?,
],
})
}
pub fn adjudicate(overrides: &SectionOverrides) -> Result<PlatformStage> {
Ok(PlatformStage {
stage: PromptStage::Adjudicate,
separator: adjudicate::SEPARATOR,
sections: vec![
overrides.rendered(adjudicate::Role::template(), &adjudicate::Role {}.values())?,
overrides.rendered(
adjudicate::Protocol::template(),
&adjudicate::Protocol {}.values(),
)?,
],
})
}
pub fn evidence_summarize(overrides: &SectionOverrides) -> Result<PlatformStage> {
Ok(PlatformStage {
stage: PromptStage::EvidenceSummarize,
separator: evidence_summarize::SEPARATOR,
sections: vec![
overrides.rendered(
evidence_summarize::Role::template(),
&evidence_summarize::Role {}.values(),
)?,
overrides.rendered(
evidence_summarize::Protocol::template(),
&evidence_summarize::Protocol {}.values(),
)?,
],
})
}
pub fn manifest() -> PromptManifest {
let mut entries = Vec::new();
for (stage, template) in compiled_platform_templates() {
entries.push(ManifestEntry {
id: PromptSectionId(template.schema.id.0.clone()),
version: PromptSectionVersion(template.schema.version.0),
stage: stage.to_string(),
role: match template.schema.role {
PromptMessageRole::System => "system".into(),
PromptMessageRole::User => "user".into(),
},
required: template.schema.required,
priority: template.schema.priority,
max_bytes: template.schema.max_bytes,
owner: "perspt-core".into(),
content_hash: template.content_hash.clone(),
activation: None,
change_record: None,
});
}
PromptManifest { entries }
}
}