Skip to main content

nemo_fabric_cli/
examples.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Maintained runnable examples composed from presets and shared assets.
5
6use std::collections::BTreeMap;
7use std::path::{Path, PathBuf};
8
9use nemo_fabric_core::{DiscoveryConfig, FabricConfig, MetadataConfig, SkillConfig};
10
11use crate::assets::{EmbeddedFile, StagedAssets};
12use crate::presets::{self, Preset};
13
14pub(crate) const CODE_REVIEW_WORKSPACE: &str =
15    include_str!("../assets/examples/code-review/repo/calculator.py");
16pub(crate) const CODE_REVIEW_SKILL: &str =
17    include_str!("../assets/examples/code-review/skills/code-review.md");
18
19const CODE_REVIEW_FILES: &[EmbeddedFile] = &[
20    EmbeddedFile {
21        path: "repo/calculator.py",
22        contents: CODE_REVIEW_WORKSPACE,
23    },
24    EmbeddedFile {
25        path: "skills/code-review.md",
26        contents: CODE_REVIEW_SKILL,
27    },
28];
29const CODE_REVIEW_VARIANTS: &[&str] = &["scripted", "hermes", "claude", "codex", "deepagents"];
30
31/// Metadata and variant catalog for one maintained CLI example.
32#[derive(Debug, Clone, Copy)]
33pub struct Example {
34    /// Stable command-line name.
35    pub name: &'static str,
36    /// Short experimentation-oriented description.
37    pub description: &'static str,
38    /// Variant selected when `--variant` is omitted.
39    pub default_variant: &'static str,
40    /// Complete supported variant names.
41    pub variants: &'static [&'static str],
42}
43
44impl Example {
45    fn preset(self, variant: Option<&str>) -> Result<Preset, String> {
46        let variant = variant.unwrap_or(self.default_variant);
47        if !self.variants.contains(&variant) {
48            return Err(format!(
49                "unknown variant {variant:?} for example {:?}; available: {}",
50                self.name,
51                self.variants.join(", ")
52            ));
53        }
54        presets::find(variant).ok_or_else(|| {
55            format!(
56                "example {:?} references missing preset {variant:?}",
57                self.name
58            )
59        })
60    }
61
62    /// Construct one complete example config without staging its assets.
63    pub fn config(self, variant: Option<&str>) -> Result<FabricConfig, String> {
64        code_review_config(self.preset(variant)?)
65    }
66
67    /// Construct and stage one complete example variant.
68    pub fn select(self, variant: Option<&str>) -> Result<SelectedExample, String> {
69        let preset = self.preset(variant)?;
70        let config = code_review_config(preset)?;
71        let files = self.embedded_files(preset);
72        let assets = StagedAssets::create(&files)
73            .map_err(|error| format!("failed to stage example {:?}: {error}", self.name))?;
74        Ok(SelectedExample {
75            example: self,
76            variant: preset,
77            config,
78            assets,
79        })
80    }
81
82    pub(crate) fn embedded_files(self, preset: Preset) -> Vec<EmbeddedFile> {
83        let mut files = Vec::from(preset.embedded_files());
84        files.extend_from_slice(CODE_REVIEW_FILES);
85        files
86    }
87}
88
89/// Selected example variant and its staged installation-safe assets.
90#[derive(Debug)]
91pub struct SelectedExample {
92    /// Example metadata.
93    pub example: Example,
94    /// Preset reused by this complete example variant.
95    pub variant: Preset,
96    /// Complete typed example configuration.
97    pub config: FabricConfig,
98    assets: StagedAssets,
99}
100
101impl SelectedExample {
102    /// Return the base directory for resolving this example.
103    pub fn base_dir(&self) -> &Path {
104        self.assets.path()
105    }
106}
107
108/// Return the maintained example catalog.
109pub fn all() -> &'static [Example] {
110    &EXAMPLES
111}
112
113/// Find an example by its stable CLI name.
114pub fn find(name: &str) -> Option<Example> {
115    EXAMPLES
116        .iter()
117        .copied()
118        .find(|example| example.name == name)
119}
120
121const EXAMPLES: [Example; 1] = [Example {
122    name: "code-review",
123    description: "Review a small Python workspace with a deterministic default or a skill-capable harness.",
124    default_variant: "scripted",
125    variants: CODE_REVIEW_VARIANTS,
126}];
127
128fn code_review_config(preset: Preset) -> Result<FabricConfig, String> {
129    let mut config = preset.config()?;
130    config.metadata = MetadataConfig {
131        name: "code-review-agent".to_string(),
132        description: Some("Maintained example for reviewing a small Python workspace.".to_string()),
133        extensions: BTreeMap::new(),
134    };
135    config.discovery = Some(DiscoveryConfig {
136        local_paths: vec![PathBuf::from("adapters")],
137        extensions: BTreeMap::new(),
138    });
139    let environment = config
140        .environment
141        .as_mut()
142        .expect("CLI presets always define an execution environment");
143    environment.workspace = Some(PathBuf::from("repo"));
144    if preset.name != "scripted" {
145        config.skills = Some(SkillConfig {
146            paths: vec![PathBuf::from("skills/code-review.md")],
147            extensions: BTreeMap::new(),
148        });
149    }
150    Ok(config)
151}
152
153#[cfg(test)]
154mod tests {
155    use nemo_fabric_core::{ResolveContext, RunRequest, resolve_run_plan_from_config, run_plan};
156
157    use super::*;
158
159    #[test]
160    fn variants_reuse_the_preset_catalog() {
161        let example = find("code-review").expect("code review example");
162        for variant in example.variants {
163            assert!(presets::find(variant).is_some(), "missing preset {variant}");
164        }
165    }
166
167    #[test]
168    fn code_review_stages_one_shared_asset_tree_and_runs() {
169        let selected = find("code-review")
170            .expect("code review example")
171            .select(Some("scripted"))
172            .expect("select example");
173        assert!(selected.base_dir().join("repo/calculator.py").is_file());
174        assert!(selected.base_dir().join("skills/code-review.md").is_file());
175        assert!(
176            selected.config.skills.is_none(),
177            "the scripted smoke variant does not declare skill support"
178        );
179        assert!(
180            find("code-review")
181                .expect("code review example")
182                .select(Some("hermes"))
183                .expect("select Hermes example")
184                .config
185                .skills
186                .is_some(),
187            "maintained harness variants consume the staged skill"
188        );
189        let plan = resolve_run_plan_from_config(
190            selected.config.clone(),
191            ResolveContext::new(selected.base_dir()),
192        )
193        .expect("plan example");
194        assert_eq!(plan.agent_name, "code-review-agent");
195        assert_eq!(
196            plan.environment_plan
197                .as_ref()
198                .expect("environment")
199                .workspace
200                .as_ref(),
201            Some(&selected.base_dir().join("repo"))
202        );
203        let result =
204            run_plan(&plan, RunRequest::text("review")).expect("run deterministic example variant");
205        assert_eq!(result.output["response"], "review");
206    }
207}