1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct PrimerConfig {
13 pub version: String,
14
15 #[serde(default)]
16 pub extends: Option<String>,
17
18 #[serde(default)]
19 pub metadata: Option<PrimerMetadata>,
20
21 #[serde(default)]
22 pub capabilities: HashMap<String, Capability>,
23
24 #[serde(default)]
25 pub categories: Vec<Category>,
26
27 pub sections: Vec<Section>,
28
29 #[serde(default)]
30 pub additional_sections: Vec<Section>,
31
32 #[serde(default)]
33 pub disabled_sections: Vec<String>,
34
35 #[serde(default)]
36 pub section_overrides: HashMap<String, SectionOverride>,
37
38 #[serde(default)]
39 pub selection_strategy: SelectionStrategy,
40
41 #[serde(default)]
42 pub output_formats: Option<OutputFormatsConfig>,
43
44 #[serde(default)]
45 pub knowledge_store: Option<KnowledgeStoreConfig>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct PrimerMetadata {
51 pub name: Option<String>,
52 pub description: Option<String>,
53 pub author: Option<String>,
54 pub license: Option<String>,
55 pub min_acp_version: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Capability {
60 pub id: String,
61 pub name: String,
62 #[serde(default)]
63 pub description: Option<String>,
64 #[serde(default)]
65 pub tools: Vec<String>,
66 #[serde(default)]
67 pub detect_command: Option<String>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct Category {
73 pub id: String,
74 pub name: String,
75 #[serde(default)]
76 pub description: Option<String>,
77 #[serde(default)]
78 pub priority: Option<u32>,
79 #[serde(default)]
80 pub color: Option<String>,
81 #[serde(default)]
82 pub icon: Option<String>,
83 #[serde(default)]
84 pub budget_constraints: Option<BudgetConstraints>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct BudgetConstraints {
90 #[serde(default)]
91 pub minimum: Option<u32>,
92 #[serde(default)]
93 pub maximum: Option<u32>,
94 #[serde(default)]
95 pub minimum_percent: Option<f32>,
96 #[serde(default)]
97 pub maximum_percent: Option<f32>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct Section {
103 pub id: String,
104
105 pub category: String,
106
107 pub tokens: TokenCount,
108
109 pub value: SectionValue,
110
111 #[serde(default)]
112 pub name: Option<String>,
113
114 #[serde(default)]
115 pub description: Option<String>,
116
117 #[serde(default)]
118 pub priority: Option<u32>,
119
120 #[serde(default)]
121 pub required: bool,
122
123 #[serde(default)]
124 pub required_if: Option<String>,
125
126 #[serde(default)]
127 pub capabilities: Vec<String>,
128
129 #[serde(default)]
130 pub capabilities_all: Vec<String>,
131
132 #[serde(default)]
133 pub depends_on: Vec<String>,
134
135 #[serde(default)]
136 pub conflicts_with: Vec<String>,
137
138 #[serde(default)]
139 pub replaces: Vec<String>,
140
141 #[serde(default)]
142 pub tags: Vec<String>,
143
144 pub formats: SectionFormats,
145
146 #[serde(default)]
147 pub data: Option<SectionData>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(untagged)]
153pub enum TokenCount {
154 Fixed(u32),
155 Dynamic(String), }
157
158impl TokenCount {
159 pub fn estimate(&self) -> u32 {
160 match self {
161 TokenCount::Fixed(n) => *n,
162 TokenCount::Dynamic(_) => 50, }
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, Default)]
169pub struct SectionValue {
170 #[serde(default)]
171 pub safety: u8,
172
173 #[serde(default)]
174 pub efficiency: u8,
175
176 #[serde(default)]
177 pub accuracy: u8,
178
179 #[serde(default = "default_base")]
180 pub base: u8,
181
182 #[serde(default)]
183 pub modifiers: Vec<ValueModifier>,
184}
185
186fn default_base() -> u8 {
187 50
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ValueModifier {
192 pub condition: String,
193
194 #[serde(default)]
195 pub add: Option<i32>,
196
197 #[serde(default)]
198 pub multiply: Option<f64>,
199
200 #[serde(default)]
201 pub set: Option<i32>,
202
203 #[serde(default)]
204 pub dimension: Option<String>,
205
206 #[serde(default)]
207 pub reason: Option<String>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, Default)]
212pub struct SectionOverride {
213 #[serde(default)]
214 pub value: Option<SectionValue>,
215
216 #[serde(default)]
217 pub tokens: Option<u32>,
218
219 #[serde(default)]
220 pub required: Option<bool>,
221
222 #[serde(default)]
223 pub required_if: Option<String>,
224
225 #[serde(default)]
226 pub formats: Option<SectionFormats>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize, Default)]
231pub struct SectionFormats {
232 #[serde(default)]
233 pub markdown: Option<FormatTemplate>,
234
235 #[serde(default)]
236 pub compact: Option<FormatTemplate>,
237
238 #[serde(default)]
239 pub json: Option<serde_json::Value>, #[serde(default)]
242 pub text: Option<FormatTemplate>,
243}
244
245impl SectionFormats {
246 pub fn get(&self, format: super::renderer::OutputFormat) -> Option<&FormatTemplate> {
248 match format {
249 super::renderer::OutputFormat::Markdown => self.markdown.as_ref(),
250 super::renderer::OutputFormat::Compact => {
251 self.compact.as_ref().or(self.markdown.as_ref())
252 }
253 super::renderer::OutputFormat::Json => None, super::renderer::OutputFormat::Text => self.text.as_ref().or(self.markdown.as_ref()),
255 }
256 }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, Default)]
260#[serde(rename_all = "camelCase")]
261pub struct FormatTemplate {
262 #[serde(default)]
263 pub template: Option<String>,
264
265 #[serde(default)]
266 pub header: Option<String>,
267
268 #[serde(default)]
269 pub footer: Option<String>,
270
271 #[serde(default)]
272 pub item_template: Option<String>,
273
274 #[serde(default)]
275 pub separator: Option<String>,
276
277 #[serde(default)]
278 pub empty_template: Option<String>,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct SectionData {
285 pub source: String,
286
287 #[serde(default)]
288 pub fields: Vec<String>,
289
290 #[serde(default)]
291 pub filter: Option<DataFilter>,
292
293 #[serde(default)]
294 pub sort_by: Option<String>,
295
296 #[serde(default)]
297 pub sort_order: Option<String>,
298
299 #[serde(default)]
300 pub max_items: Option<usize>,
301
302 #[serde(default)]
303 pub item_tokens: Option<u32>,
304
305 #[serde(default)]
306 pub empty_behavior: Option<String>,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
310#[serde(untagged)]
311pub enum DataFilter {
312 Array(Vec<String>),
313 Object(HashMap<String, serde_json::Value>),
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318#[serde(rename_all = "camelCase")]
319pub struct SelectionStrategy {
320 #[serde(default = "default_algorithm")]
321 pub algorithm: String,
322
323 #[serde(default)]
324 pub weights: DimensionWeights,
325
326 #[serde(default)]
327 pub presets: HashMap<String, DimensionWeights>,
328
329 #[serde(default)]
330 pub phases: Vec<SelectionPhase>,
331
332 #[serde(default = "default_min_budget")]
333 pub minimum_budget: u32,
334
335 #[serde(default = "default_dynamic_enabled")]
336 pub dynamic_modifiers_enabled: bool,
337}
338
339impl Default for SelectionStrategy {
340 fn default() -> Self {
341 Self {
342 algorithm: default_algorithm(),
343 weights: DimensionWeights::default(),
344 presets: HashMap::new(),
345 phases: Vec::new(),
346 minimum_budget: default_min_budget(),
347 dynamic_modifiers_enabled: default_dynamic_enabled(),
348 }
349 }
350}
351
352fn default_algorithm() -> String {
353 "value-optimized".to_string()
354}
355
356fn default_min_budget() -> u32 {
357 80
358}
359
360fn default_dynamic_enabled() -> bool {
361 true
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct DimensionWeights {
367 #[serde(default = "default_safety_weight")]
368 pub safety: f64,
369
370 #[serde(default = "default_weight")]
371 pub efficiency: f64,
372
373 #[serde(default = "default_weight")]
374 pub accuracy: f64,
375
376 #[serde(default = "default_weight")]
377 pub base: f64,
378}
379
380impl Default for DimensionWeights {
381 fn default() -> Self {
382 Self {
383 safety: default_safety_weight(),
384 efficiency: default_weight(),
385 accuracy: default_weight(),
386 base: default_weight(),
387 }
388 }
389}
390
391fn default_safety_weight() -> f64 {
392 1.5
393}
394
395fn default_weight() -> f64 {
396 1.0
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401#[serde(rename_all = "camelCase")]
402pub struct SelectionPhase {
403 pub name: String,
404
405 #[serde(default)]
406 pub filter: PhaseFilter,
407
408 #[serde(default = "default_sort")]
409 pub sort: String,
410
411 #[serde(default)]
412 pub budget_percent: Option<f32>,
413}
414
415fn default_sort() -> String {
416 "value-per-token".to_string()
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize, Default)]
420#[serde(rename_all = "camelCase")]
421pub struct PhaseFilter {
422 #[serde(default)]
423 pub required: Option<bool>,
424
425 #[serde(default)]
426 pub required_if: Option<bool>,
427
428 #[serde(default)]
429 pub safety_minimum: Option<u8>,
430
431 #[serde(default)]
432 pub categories: Option<Vec<String>>,
433
434 #[serde(default)]
435 pub tags: Option<Vec<String>>,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize, Default)]
440pub struct OutputFormatsConfig {
441 #[serde(default)]
442 pub markdown: Option<MarkdownConfig>,
443
444 #[serde(default)]
445 pub compact: Option<CompactConfig>,
446
447 #[serde(default)]
448 pub json: Option<JsonConfig>,
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize)]
452#[serde(rename_all = "camelCase")]
453pub struct MarkdownConfig {
454 #[serde(default)]
455 pub section_separator: Option<String>,
456 #[serde(default)]
457 pub header_level: Option<u8>,
458 #[serde(default)]
459 pub list_style: Option<String>,
460 #[serde(default)]
461 pub code_block_style: Option<String>,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
465#[serde(rename_all = "camelCase")]
466pub struct CompactConfig {
467 #[serde(default)]
468 pub section_separator: Option<String>,
469 #[serde(default)]
470 pub max_line_length: Option<usize>,
471 #[serde(default)]
472 pub abbreviate: Option<bool>,
473}
474
475#[derive(Debug, Clone, Serialize, Deserialize)]
476#[serde(rename_all = "camelCase")]
477pub struct JsonConfig {
478 #[serde(default)]
479 pub pretty: Option<bool>,
480 #[serde(default)]
481 pub include_metadata: Option<bool>,
482 #[serde(default)]
483 pub include_token_counts: Option<bool>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
488#[serde(rename_all = "camelCase")]
489pub struct KnowledgeStoreConfig {
490 #[serde(default)]
491 pub enabled: Option<bool>,
492 #[serde(default)]
493 pub index_path: Option<String>,
494 #[serde(default)]
495 pub semantic_db_path: Option<String>,
496 #[serde(default)]
497 pub fallback_to_index: Option<bool>,
498}
499
500#[derive(Debug, Clone)]
502pub struct SelectedSection {
503 pub id: String,
504 pub priority: u32,
505 pub tokens: u32,
506 pub value: f64,
507 pub section: Section,
508}
509
510#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
512#[serde(rename_all = "lowercase")]
513pub enum PrimerTier {
514 Micro,
516 Minimal,
518 Standard,
520 Full,
522}
523
524impl PrimerTier {
525 pub fn from_budget(budget: u32) -> Self {
533 match budget {
534 0..=299 => PrimerTier::Micro,
535 300..=449 => PrimerTier::Minimal,
536 450..=699 => PrimerTier::Standard,
537 _ => PrimerTier::Full,
538 }
539 }
540
541 pub fn name(&self) -> &'static str {
543 match self {
544 PrimerTier::Micro => "micro",
545 PrimerTier::Minimal => "minimal",
546 PrimerTier::Standard => "standard",
547 PrimerTier::Full => "full",
548 }
549 }
550
551 pub fn cli_tokens(&self) -> u32 {
553 match self {
554 PrimerTier::Micro => 250,
555 PrimerTier::Minimal => 400,
556 PrimerTier::Standard => 600,
557 PrimerTier::Full => 1400,
558 }
559 }
560
561 pub fn mcp_tokens(&self) -> u32 {
563 match self {
564 PrimerTier::Micro => 178,
565 PrimerTier::Minimal => 320,
566 PrimerTier::Standard => 480,
567 PrimerTier::Full => 1100,
568 }
569 }
570
571 pub fn description(&self) -> &'static str {
573 match self {
574 PrimerTier::Micro => "Minimal context, action-focused",
575 PrimerTier::Minimal => "Standard IDE integrations",
576 PrimerTier::Standard => "Full-featured agents (recommended)",
577 PrimerTier::Full => "Dedicated context budget, raw API",
578 }
579 }
580
581 pub fn all() -> impl Iterator<Item = PrimerTier> {
583 [
584 PrimerTier::Micro,
585 PrimerTier::Minimal,
586 PrimerTier::Standard,
587 PrimerTier::Full,
588 ]
589 .into_iter()
590 }
591}
592
593impl std::fmt::Display for PrimerTier {
594 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
595 write!(f, "{}", self.name())
596 }
597}