Skip to main content

oxios_kernel/program/
parser.rs

1//! Program TOML parsing — load program metadata from program.toml files.
2
3use std::collections::HashMap;
4use std::fs;
5use std::path::Path;
6
7use anyhow::{Context, Result};
8
9use super::types::{ArgumentDef, McpServerConfig, ProgramHostRequirements, ProgramMeta, ToolDef};
10
11/// Parsed program.toml structure
12#[derive(Debug, Clone, serde::Deserialize)]
13struct TomlProgram {
14    program: TomlProgramInfo,
15    tools: Option<HashMap<String, TomlTool>>,
16    #[serde(rename = "host_requirements")]
17    host_requirements: Option<TomlHostRequirements>,
18    #[serde(rename = "requires_tools")]
19    requires_tools: Option<TomlRequiresTools>,
20    #[serde(rename = "mcp", default)]
21    mcp: Option<Vec<McpServerConfig>>,
22}
23
24#[derive(Debug, Clone, serde::Deserialize)]
25struct TomlProgramInfo {
26    name: String,
27    version: String,
28    description: String,
29    author: String,
30}
31
32#[derive(Debug, Clone, serde::Deserialize)]
33struct TomlTool {
34    description: String,
35    /// Command to execute (first word = binary, rest = default args)
36    #[serde(default)]
37    command: String,
38    arguments: Option<Vec<TomlArgument>>,
39}
40
41#[derive(Debug, Clone, serde::Deserialize)]
42struct TomlArgument {
43    name: String,
44    description: String,
45    required: Option<bool>,
46    default: Option<String>,
47}
48
49#[derive(Debug, Clone, serde::Deserialize)]
50struct TomlHostRequirements {
51    required: Option<Vec<String>>,
52    optional: Option<Vec<String>>,
53}
54
55/// Required tools for a program to function.
56#[derive(Debug, Clone, serde::Deserialize)]
57struct TomlRequiresTools {
58    names: Vec<String>,
59}
60
61impl ProgramMeta {
62    /// Load program metadata from a directory
63    pub fn load_from_dir(path: &Path) -> Result<Self> {
64        let toml_path = path.join("program.toml");
65        let content = fs::read_to_string(&toml_path)
66            .with_context(|| format!("Failed to read {}", toml_path.display()))?;
67
68        let toml: TomlProgram = toml::from_str(&content)
69            .with_context(|| format!("Failed to parse {}", toml_path.display()))?;
70
71        let tools = toml
72            .tools
73            .map(|t| {
74                t.into_iter()
75                    .map(|(name, tool)| {
76                        let arguments = tool
77                            .arguments
78                            .unwrap_or_default()
79                            .into_iter()
80                            .map(|arg| ArgumentDef {
81                                name: arg.name,
82                                description: arg.description,
83                                required: arg.required.unwrap_or(true),
84                                default: arg.default,
85                            })
86                            .collect();
87                        ToolDef {
88                            name,
89                            description: tool.description,
90                            arguments,
91                            command: tool.command,
92                        }
93                    })
94                    .collect()
95            })
96            .unwrap_or_default();
97
98        let host_requirements = toml
99            .host_requirements
100            .map(|hr| ProgramHostRequirements {
101                required: hr.required.unwrap_or_default(),
102                optional: hr.optional.unwrap_or_default(),
103            })
104            .unwrap_or_default();
105
106        let dependencies = toml.requires_tools.map(|rt| rt.names).unwrap_or_default();
107
108        let mcp_servers = toml.mcp.unwrap_or_default();
109
110        Ok(ProgramMeta {
111            name: toml.program.name,
112            version: toml.program.version,
113            description: toml.program.description,
114            author: toml.program.author,
115            tools,
116            dependencies,
117            host_requirements,
118            mcp_servers,
119        })
120    }
121}