Skip to main content

apollo/plugins/
manifest.rs

1//! JSON manifest under `workspace/.apollo/plugins/manifest.json`.
2
3use std::path::Path;
4
5use anyhow::Context;
6use serde::Deserialize;
7
8use crate::config::Config;
9use crate::tools::toolsets::apply_package_manifest;
10
11#[derive(Debug, Clone, Deserialize, Default)]
12pub struct PluginManifestFile {
13    #[serde(default)]
14    pub version: u32,
15    #[serde(default)]
16    pub packages: Vec<String>,
17    #[serde(default)]
18    pub toolsets: ToolsetPatch,
19    #[serde(default)]
20    pub system_prompt_suffix: String,
21}
22
23#[derive(Debug, Clone, Deserialize, Default)]
24pub struct ToolsetPatch {
25    #[serde(default)]
26    pub enabled: Vec<String>,
27    #[serde(default)]
28    pub disabled: Vec<String>,
29}
30
31pub fn load_manifest(workspace: &Path, cfg: &Config) -> anyhow::Result<Option<PluginManifestFile>> {
32    if !cfg.plugin_layer.enabled {
33        return Ok(None);
34    }
35    let path = workspace
36        .join(".apollo")
37        .join(&cfg.plugin_layer.manifest_path);
38    if !path.is_file() {
39        return Ok(None);
40    }
41    let raw = std::fs::read_to_string(&path)
42        .with_context(|| format!("read plugin manifest {}", path.display()))?;
43    let m: PluginManifestFile = serde_json::from_str(&raw)
44        .with_context(|| format!("parse plugin manifest {}", path.display()))?;
45    Ok(Some(m))
46}
47
48pub fn merge_manifest_into_config(cfg: &mut Config, manifest: &PluginManifestFile) {
49    apply_package_manifest(&mut cfg.toolsets, &manifest.packages);
50    for e in &manifest.toolsets.enabled {
51        if !cfg.toolsets.enabled.contains(e) {
52            cfg.toolsets.enabled.push(e.clone());
53        }
54    }
55    for d in &manifest.toolsets.disabled {
56        if !cfg.toolsets.disabled.contains(d) {
57            cfg.toolsets.disabled.push(d.clone());
58        }
59    }
60    if !manifest.system_prompt_suffix.trim().is_empty() {
61        cfg.system_prompt.push('\n');
62        cfg.system_prompt
63            .push_str(manifest.system_prompt_suffix.trim());
64    }
65}
66
67pub fn apply_workspace_manifest(cfg: &mut Config, workspace: &Path) {
68    match load_manifest(workspace, cfg) {
69        Ok(Some(m)) => merge_manifest_into_config(cfg, &m),
70        Ok(None) => {}
71        Err(e) => tracing::warn!("plugin manifest skipped: {:#}", e),
72    }
73}