1use perspt_sdk::error::Result;
14use perspt_sdk::prompt::{
15 CompiledPromptProgram, ManifestEntry, ModelDialect, PromptManifest, PromptMessageRole,
16 PromptRoute, PromptSectionId, PromptSectionVersion, PromptStage, RenderedSection,
17 SectionTemplate, StageComposition,
18};
19
20mod generated {
21 include!(concat!(env!("OUT_DIR"), "/prompt_sections.rs"));
22}
23
24pub use generated::{
25 adjudicate, evidence_summarize, graph_plan, repository_explore, session_bootstrap,
26};
27
28#[derive(Debug, Clone, Default)]
36pub struct SectionOverrides {
37 map: std::collections::BTreeMap<String, perspt_sdk::prompt::SectionTemplate>,
38}
39
40impl SectionOverrides {
41 pub fn insert_replacement(&mut self, id: &str, body: &str) -> Result<()> {
45 let base = compiled_platform_templates()
46 .into_iter()
47 .map(|(_, template)| template)
48 .find(|template| template.schema.id.0 == id)
49 .ok_or_else(|| {
50 perspt_sdk::error::SdkError::Domain(format!(
51 "section {id:?} is not a compiled platform section; live \
52 substitution covers the five platform stages only"
53 ))
54 })?;
55 let template = perspt_sdk::prompt::SectionTemplate {
56 schema: base.schema,
57 content_hash: perspt_sdk::prompt::SectionTemplate::hash_body(body),
58 body: body.to_string(),
59 };
60 self.map.insert(id.to_string(), template);
61 Ok(())
62 }
63
64 pub fn is_empty(&self) -> bool {
65 self.map.is_empty()
66 }
67
68 pub fn provenance(&self) -> Vec<(String, String)> {
70 self.map
71 .iter()
72 .map(|(id, template)| (id.clone(), template.content_hash.clone()))
73 .collect()
74 }
75
76 fn rendered(
81 &self,
82 base: perspt_sdk::prompt::SectionTemplate,
83 values: &std::collections::BTreeMap<String, perspt_sdk::prompt::VarValue>,
84 ) -> Result<RenderedSection> {
85 match self.map.get(base.schema.id.0.as_str()) {
86 Some(replacement) => replacement.render(values),
87 None => base.render(values),
88 }
89 }
90}
91
92pub fn compiled_platform_templates() -> Vec<(&'static str, SectionTemplate)> {
94 let stages: [(&'static str, Vec<SectionTemplate>); 5] = [
95 ("adjudicate", adjudicate::templates()),
96 ("evidence_summarize", evidence_summarize::templates()),
97 ("graph_plan", graph_plan::templates()),
98 ("repository_explore", repository_explore::templates()),
99 ("session_bootstrap", session_bootstrap::templates()),
100 ];
101 let mut templates = Vec::new();
102 for (stage, list) in stages {
103 for template in list {
104 templates.push((stage, template));
105 }
106 }
107 templates
108}
109
110#[derive(Debug, Clone)]
112pub struct PlatformStage {
113 pub stage: PromptStage,
114 pub separator: &'static str,
117 pub sections: Vec<RenderedSection>,
118}
119
120impl PlatformStage {
121 pub fn compile(
125 &self,
126 route: &PromptRoute,
127 dialect: &ModelDialect,
128 tool_spec_hash: &str,
129 ) -> Result<CompiledPromptProgram> {
130 let mut composition = StageComposition::new(self.stage).system_separator(self.separator);
131 for section in &self.sections {
132 composition =
133 composition.resolved(section.clone(), perspt_sdk::prompt::OverrideOrigin::Base);
134 }
135 composition.compile(route, dialect, tool_spec_hash)
136 }
137}
138
139pub fn compile_invocation(
143 stage: &PlatformStage,
144 domain_sections: &[RenderedSection],
145 route: &PromptRoute,
146 dialect: &ModelDialect,
147 tool_spec_hash: &str,
148) -> Result<perspt_sdk::prompt::CompiledPromptInvocation> {
149 let platform = stage.compile(route, dialect, tool_spec_hash)?;
150 let mut composition = StageComposition::new(stage.stage);
151 for section in domain_sections {
152 composition =
153 composition.resolved(section.clone(), perspt_sdk::prompt::OverrideOrigin::Base);
154 }
155 let domain = composition.compile(route, dialect, tool_spec_hash)?;
156 Ok(perspt_sdk::prompt::CompiledPromptInvocation::new(
157 platform, domain,
158 ))
159}
160
161#[derive(Debug)]
163pub struct PlatformPromptLibrary;
164
165impl PlatformPromptLibrary {
166 pub fn session_bootstrap(
169 domain_id: &str,
170 overrides: &SectionOverrides,
171 ) -> Result<PlatformStage> {
172 let role = session_bootstrap::Role {
173 domain_id: perspt_sdk::prompt::BoundedText::new(domain_id)?,
174 };
175 Ok(PlatformStage {
176 stage: PromptStage::SessionBootstrap,
177 separator: session_bootstrap::SEPARATOR,
178 sections: vec![
179 overrides.rendered(session_bootstrap::Role::template(), &role.values())?,
180 overrides.rendered(
181 session_bootstrap::Governance::template(),
182 &session_bootstrap::Governance {}.values(),
183 )?,
184 ],
185 })
186 }
187
188 pub fn graph_plan(revision_shape: &str, overrides: &SectionOverrides) -> Result<PlatformStage> {
192 let protocol = graph_plan::UpdateProtocol {
193 revision_shape: perspt_sdk::prompt::BoundedText::new(revision_shape)?,
194 };
195 Ok(PlatformStage {
196 stage: PromptStage::GraphPlan,
197 separator: graph_plan::SEPARATOR,
198 sections: vec![
199 overrides.rendered(graph_plan::Role::template(), &graph_plan::Role {}.values())?,
200 overrides.rendered(graph_plan::UpdateProtocol::template(), &protocol.values())?,
201 ],
202 })
203 }
204
205 pub fn repository_explore(overrides: &SectionOverrides) -> Result<PlatformStage> {
206 Ok(PlatformStage {
207 stage: PromptStage::RepositoryExplore,
208 separator: repository_explore::SEPARATOR,
209 sections: vec![
210 overrides.rendered(
211 repository_explore::Role::template(),
212 &repository_explore::Role {}.values(),
213 )?,
214 overrides.rendered(
215 repository_explore::Protocol::template(),
216 &repository_explore::Protocol {}.values(),
217 )?,
218 ],
219 })
220 }
221
222 pub fn adjudicate(overrides: &SectionOverrides) -> Result<PlatformStage> {
223 Ok(PlatformStage {
224 stage: PromptStage::Adjudicate,
225 separator: adjudicate::SEPARATOR,
226 sections: vec![
227 overrides.rendered(adjudicate::Role::template(), &adjudicate::Role {}.values())?,
228 overrides.rendered(
229 adjudicate::Protocol::template(),
230 &adjudicate::Protocol {}.values(),
231 )?,
232 ],
233 })
234 }
235
236 pub fn evidence_summarize(overrides: &SectionOverrides) -> Result<PlatformStage> {
237 Ok(PlatformStage {
238 stage: PromptStage::EvidenceSummarize,
239 separator: evidence_summarize::SEPARATOR,
240 sections: vec![
241 overrides.rendered(
242 evidence_summarize::Role::template(),
243 &evidence_summarize::Role {}.values(),
244 )?,
245 overrides.rendered(
246 evidence_summarize::Protocol::template(),
247 &evidence_summarize::Protocol {}.values(),
248 )?,
249 ],
250 })
251 }
252
253 pub fn manifest() -> PromptManifest {
255 let mut entries = Vec::new();
256 for (stage, template) in compiled_platform_templates() {
257 entries.push(ManifestEntry {
258 id: PromptSectionId(template.schema.id.0.clone()),
259 version: PromptSectionVersion(template.schema.version.0),
260 stage: stage.to_string(),
261 role: match template.schema.role {
262 PromptMessageRole::System => "system".into(),
263 PromptMessageRole::User => "user".into(),
264 },
265 required: template.schema.required,
266 priority: template.schema.priority,
267 max_bytes: template.schema.max_bytes,
268 owner: "perspt-core".into(),
269 content_hash: template.content_hash.clone(),
270 activation: None,
271 change_record: None,
272 });
273 }
274 PromptManifest { entries }
275 }
276}