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}