Skip to main content

intr_parser/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use thiserror::Error;
4
5// ---------------------------------------------------------------------------
6// Parse result
7// ---------------------------------------------------------------------------
8
9/// Output of parsing a `.prompt` file.
10///
11/// The `tier` field indicates the structured richness of the file:
12/// - `1` - plain template body only, no frontmatter.
13/// - `2` - YAML frontmatter present with at least `id` + `version`.
14/// - `3` - Tier 2 + `evals` and/or `chains_to`.
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct ParseResult {
17    /// Detected tier (1, 2, or 3).
18    pub tier: u8,
19
20    /// Parsed YAML frontmatter. `None` for Tier 1 files.
21    pub frontmatter: Option<Frontmatter>,
22
23    /// The Handlebars template body (everything after the closing `---`).
24    pub body: String,
25
26    /// Variables extracted from `{{variable}}` markers in the body.
27    /// De-duplicated, sorted alphabetically.
28    pub variables: Vec<String>,
29
30    /// Non-fatal issues encountered during parsing.
31    pub warnings: Vec<ParseWarning>,
32}
33
34// ---------------------------------------------------------------------------
35// Frontmatter
36// ---------------------------------------------------------------------------
37
38/// YAML frontmatter between `---` fences.
39///
40/// Fields follow the Dotprompt spec + Intentry extensions under the `intentry:` key.
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct Frontmatter {
44    /// Stable kebab-case identifier, unique within a Space. Max 64 chars.
45    /// Required for Tier 2+.
46    pub id: Option<String>,
47
48    /// Semver version string (e.g. `"1.2.0"`). Required for Tier 2+.
49    pub version: Option<String>,
50
51    /// One-line human description of what the prompt does.
52    pub description: Option<String>,
53
54    /// Model preferences and generation parameters.
55    pub model: Option<ModelHints>,
56
57    /// Input variable declarations.
58    pub input: Option<InputSpec>,
59
60    /// Output shape expectations (for evals and schema validation).
61    pub output: Option<OutputSpec>,
62
63    /// Eval cases. Presence of this field (non-empty) promotes to Tier 3.
64    pub evals: Option<Vec<Eval>>,
65
66    /// Intentry-specific extensions (tags, license, parent fork info, etc.).
67    pub intentry: Option<IntrEntryMeta>,
68
69    /// Any unknown fields are preserved as raw JSON for forward-compatibility.
70    #[serde(flatten)]
71    pub extra: HashMap<String, serde_json::Value>,
72}
73
74// ---------------------------------------------------------------------------
75// Model hints
76// ---------------------------------------------------------------------------
77
78/// Model preferences for executing this prompt.
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct ModelHints {
81    /// Ordered list of preferred model IDs. First supported model is used.
82    /// Example: `["claude-sonnet-4-6", "gpt-4o"]`
83    pub preferred: Option<Vec<String>>,
84
85    /// Sampling temperature (0.0–2.0).
86    pub temperature: Option<f64>,
87
88    /// Maximum tokens to generate.
89    pub max_tokens: Option<u32>,
90
91    /// Top-p nucleus sampling parameter.
92    pub top_p: Option<f64>,
93
94    /// Stop sequences.
95    pub stop: Option<Vec<String>>,
96
97    /// Any model-specific parameters not covered above are passed through.
98    #[serde(flatten)]
99    pub extra: HashMap<String, serde_json::Value>,
100}
101
102// ---------------------------------------------------------------------------
103// Input / Output specs (Picoschema)
104// ---------------------------------------------------------------------------
105
106/// Input variable declarations.
107#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub struct InputSpec {
109    /// Picoschema describing the expected input variables.
110    pub schema: Option<Picoschema>,
111}
112
113/// Output shape expectations.
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct OutputSpec {
116    /// Picoschema describing the expected output structure.
117    pub schema: Option<Picoschema>,
118
119    /// Output format hint: `"text"`, `"json"`, `"markdown"`.
120    pub format: Option<String>,
121}
122
123/// Picoschema type.
124///
125/// Picoschema is the lightweight schema format from Dotprompt. In Intentry it is
126/// represented as raw JSON (parsed from YAML) to keep the parser simple and to
127/// remain forward-compatible as the schema language evolves.
128///
129/// Common examples:
130/// ```yaml
131/// schema:
132///   name: string        # required string field
133///   age?: number        # optional number field
134///   tags: string[]      # array of strings
135/// ```
136pub type Picoschema = serde_json::Value;
137
138// ---------------------------------------------------------------------------
139// Evals (Tier 3)
140// ---------------------------------------------------------------------------
141
142/// A single eval test case.
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct Eval {
145    /// Human-readable description of what this eval is testing.
146    pub description: Option<String>,
147
148    /// Input variables for this eval run.
149    pub input: serde_json::Value,
150
151    /// Assertions to check against the model's output.
152    pub expect: Option<EvalExpectation>,
153}
154
155/// Assertions for an eval.
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157pub struct EvalExpectation {
158    /// The output must contain this substring.
159    pub contains: Option<String>,
160
161    /// The output must not contain this substring.
162    pub not_contains: Option<String>,
163
164    /// The output must exactly equal this string.
165    pub equals: Option<String>,
166
167    /// A JSON schema the output must validate against (if format is JSON).
168    pub json_schema: Option<serde_json::Value>,
169
170    /// Any additional custom assertions (forward-compatibility).
171    #[serde(flatten)]
172    pub extra: HashMap<String, serde_json::Value>,
173}
174
175// ---------------------------------------------------------------------------
176// Intentry-specific metadata
177// ---------------------------------------------------------------------------
178
179/// Intentry-specific frontmatter extensions under the `intentry:` key.
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct IntrEntryMeta {
182    /// Searchable tags for the commons.
183    pub tags: Option<Vec<String>>,
184
185    /// SPDX license identifier for the prompt content.
186    pub license: Option<String>,
187
188    /// Fork attribution: `"<author>/<slug>@<version>"`.
189    pub parent: Option<String>,
190
191    /// ISO 8601 timestamp when this fork was created.
192    pub forked_at: Option<String>,
193
194    /// Any unknown fields are preserved.
195    #[serde(flatten)]
196    pub extra: HashMap<String, serde_json::Value>,
197}
198
199// ---------------------------------------------------------------------------
200// Diagnostics
201// ---------------------------------------------------------------------------
202
203/// A non-fatal issue encountered during parsing.
204///
205/// Warnings do not prevent a file from being committed, but they may reduce
206/// platform functionality (e.g. a missing `description` suppresses indexing).
207#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
208pub struct ParseWarning {
209    /// Machine-readable warning code (e.g. `"missing_description"`).
210    pub code: String,
211    /// Human-readable message.
212    pub message: String,
213}
214
215/// A fatal parse error.
216#[derive(Debug, Clone, Error, PartialEq)]
217pub enum ParseError {
218    /// The file is not valid UTF-8.
219    #[error("file is not valid UTF-8: {0}")]
220    InvalidUtf8(String),
221
222    /// The YAML frontmatter could not be parsed.
223    #[error("invalid YAML frontmatter: {0}")]
224    InvalidFrontmatter(String),
225
226    /// A Tier 2 field has an invalid value (e.g. `version` is not valid semver).
227    #[error("invalid field '{field}': {reason}")]
228    InvalidField { field: String, reason: String },
229
230    /// The file exceeds the 1 MB hard size limit.
231    #[error("file too large: {size} bytes (max 1 MB)")]
232    FileTooLarge { size: usize },
233}