195 lines
7.3 KiB
Rust
195 lines
7.3 KiB
Rust
use config_source::{ConfigProjectionValidator, ConfigSchemaContribution};
|
|
use worker::{EffectivePromptCatalog, prompt_schema_source};
|
|
|
|
use crate::config_source::{
|
|
WorkspaceConfigSchemaProvider, WorkspaceConfigState, evaluate_workspace_config_state,
|
|
};
|
|
use crate::{Error, Result};
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct PromptConfigSchemaProvider;
|
|
|
|
impl WorkspaceConfigSchemaProvider for PromptConfigSchemaProvider {
|
|
fn contribution(&self) -> Result<ConfigSchemaContribution> {
|
|
ConfigSchemaContribution::new(
|
|
"builtin:prompts",
|
|
"prompts",
|
|
"1",
|
|
prompt_schema_source().map_err(|error| Error::Config(error.to_string()))?,
|
|
)
|
|
.map(|contribution| {
|
|
contribution.with_projection_validator(
|
|
ConfigProjectionValidator::StaticTemplateCatalog {
|
|
namespace: "prompts".to_string(),
|
|
key_aliases: std::collections::BTreeMap::from([(
|
|
"default_prompt".to_string(),
|
|
"default".to_string(),
|
|
)]),
|
|
},
|
|
)
|
|
})
|
|
.map_err(|error| Error::Config(error.to_string()))
|
|
}
|
|
}
|
|
|
|
pub fn validate_evaluated_prompt_catalog(
|
|
evaluation: &config_source::EvaluationResult,
|
|
) -> Result<()> {
|
|
let projection = evaluation.projections.first().ok_or_else(|| {
|
|
Error::InvalidInput("Workspace config produced no active projection".to_string())
|
|
})?;
|
|
let prompts = projection.data_json.get("prompts").ok_or_else(|| {
|
|
Error::InvalidInput("Workspace config projection has no prompts namespace".to_string())
|
|
})?;
|
|
EffectivePromptCatalog::from_projection(prompts, 0, "preview", "preview")
|
|
.map(|_| ())
|
|
.map_err(|error| Error::InvalidInput(format!("invalid Prompt catalog: {error}")))
|
|
}
|
|
|
|
pub fn project_prompts_from_workspace_config(
|
|
state: &WorkspaceConfigState,
|
|
) -> Result<EffectivePromptCatalog> {
|
|
let evaluation = evaluate_workspace_config_state(state, state.contract.schema_bundle.clone())?;
|
|
if evaluation.projection_digest != state.projection_digest {
|
|
return Err(Error::RegistryInconsistency(
|
|
"Prompt projection digest does not match the active Workspace config revision"
|
|
.to_string(),
|
|
));
|
|
}
|
|
let projection = evaluation.projections.first().ok_or_else(|| {
|
|
Error::RegistryInconsistency("Workspace config has no active projection".to_string())
|
|
})?;
|
|
let prompts = projection.data_json.get("prompts").ok_or_else(|| {
|
|
Error::RegistryInconsistency(
|
|
"active Workspace config projection has no prompts namespace".to_string(),
|
|
)
|
|
})?;
|
|
EffectivePromptCatalog::from_projection(
|
|
prompts,
|
|
state.snapshot.revision,
|
|
state.contract.schema_bundle.fingerprint.clone(),
|
|
state.contract.fingerprint.clone(),
|
|
)
|
|
.map_err(|error| Error::RegistryInconsistency(error.to_string()))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use config_source::{
|
|
ConfigContentType, ConfigEntry, ConfigTreeSnapshot, SnapshotEnvironment, ToolchainContract,
|
|
VirtualPath, WorkspaceConfigSchemaBundle,
|
|
};
|
|
|
|
fn state(source: &str) -> WorkspaceConfigState {
|
|
let schema = WorkspaceConfigSchemaBundle::compose([PromptConfigSchemaProvider
|
|
.contribution()
|
|
.unwrap()])
|
|
.unwrap();
|
|
let snapshot = ConfigTreeSnapshot::from_entries(
|
|
7,
|
|
[ConfigEntry::new(
|
|
VirtualPath::parse("main.dcdl").unwrap(),
|
|
ConfigContentType::Decodal,
|
|
source,
|
|
)
|
|
.unwrap()],
|
|
)
|
|
.unwrap();
|
|
let contract = ToolchainContract::with_schema_bundle(
|
|
config_source::DEFAULT_SCHEMA_VERSION,
|
|
vec![VirtualPath::parse("main.dcdl").unwrap()],
|
|
config_source::DEFAULT_IMPORT_POLICY_VERSION,
|
|
schema,
|
|
);
|
|
let projection_digest = SnapshotEnvironment::new(snapshot.clone())
|
|
.evaluate_contract(&contract)
|
|
.unwrap()
|
|
.projection_digest;
|
|
WorkspaceConfigState {
|
|
snapshot,
|
|
contract,
|
|
projection_digest,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn workspace_override_deep_patches_builtin_and_preserves_other_leaves() {
|
|
let state = state(r#"{ prompts = { common = { language = "OVERRIDE"; }; }; }"#);
|
|
let catalog = project_prompts_from_workspace_config(&state).unwrap();
|
|
assert_eq!(catalog.config_revision, 7);
|
|
assert_eq!(catalog.templates["common.language"], "OVERRIDE");
|
|
assert!(!catalog.templates["common.workspace"].is_empty());
|
|
assert!(catalog.templates["default"].contains("common.workspace"));
|
|
}
|
|
|
|
#[test]
|
|
fn preview_commit_validator_rejects_dynamic_missing_and_cyclic_includes() {
|
|
let schema = WorkspaceConfigSchemaBundle::compose([PromptConfigSchemaProvider
|
|
.contribution()
|
|
.unwrap()])
|
|
.unwrap();
|
|
for source in [
|
|
r#"{ prompts = { common = { language = "{%- include target -%}"; }; }; }"#,
|
|
r#"{ prompts = { common = { language = "{%- include \"missing\" -%}"; }; }; }"#,
|
|
r#"{ prompts = { common = { language = "{% include \"common.workspace\" %}"; workspace = "{% include \"common.language\" %}"; }; }; }"#,
|
|
] {
|
|
let snapshot = ConfigTreeSnapshot::from_entries(
|
|
0,
|
|
[ConfigEntry::new(
|
|
VirtualPath::parse("main.dcdl").unwrap(),
|
|
ConfigContentType::Decodal,
|
|
source,
|
|
)
|
|
.unwrap()],
|
|
)
|
|
.unwrap();
|
|
let contract = ToolchainContract::with_schema_bundle(
|
|
config_source::DEFAULT_SCHEMA_VERSION,
|
|
vec![VirtualPath::parse("main.dcdl").unwrap()],
|
|
config_source::DEFAULT_IMPORT_POLICY_VERSION,
|
|
schema.clone(),
|
|
);
|
|
assert!(
|
|
SnapshotEnvironment::new(snapshot)
|
|
.evaluate_contract(&contract)
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn closed_prompt_schema_rejects_unknown_and_non_string_leaves() {
|
|
let schema = WorkspaceConfigSchemaBundle::compose([PromptConfigSchemaProvider
|
|
.contribution()
|
|
.unwrap()])
|
|
.unwrap();
|
|
for source in [
|
|
"{ prompts = { common = { unknown = \"bad\"; }; }; }",
|
|
"{ prompts = { common = { language = 42; }; }; }",
|
|
] {
|
|
let snapshot = ConfigTreeSnapshot::from_entries(
|
|
0,
|
|
[ConfigEntry::new(
|
|
VirtualPath::parse("main.dcdl").unwrap(),
|
|
ConfigContentType::Decodal,
|
|
source,
|
|
)
|
|
.unwrap()],
|
|
)
|
|
.unwrap();
|
|
let contract = ToolchainContract::with_schema_bundle(
|
|
config_source::DEFAULT_SCHEMA_VERSION,
|
|
vec![VirtualPath::parse("main.dcdl").unwrap()],
|
|
config_source::DEFAULT_IMPORT_POLICY_VERSION,
|
|
schema.clone(),
|
|
);
|
|
assert!(
|
|
SnapshotEnvironment::new(snapshot)
|
|
.evaluate_contract(&contract)
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|
|
}
|