robonix_executor/
config.rs1use anyhow::{Context, Result};
8use clap::Parser;
9use serde::Deserialize;
10use std::path::{Path, PathBuf};
11
12pub const DEFAULT_EXECUTOR_PROVIDER_ID: &str = "executor";
13pub const EXECUTOR_NAMESPACE: &str = "robonix/system/executor";
14pub const DEFAULT_ATLAS_ENDPOINT: &str = "127.0.0.1:50051";
15pub const DEFAULT_LISTEN: &str = "127.0.0.1:50061";
16
17#[derive(Debug, Clone)]
18pub struct ExecutorConfig {
19 pub atlas_endpoint: String,
20 pub listen: String,
21 pub id: String,
22}
23
24#[derive(Parser, Debug)]
25#[command(
26 name = "robonix-executor",
27 about = "Robonix Executor — tool-call dispatch runtime"
28)]
29pub struct Args {
30 #[arg(long, env = "ROBONIX_ATLAS_ENDPOINT")]
33 pub atlas: Option<String>,
34
35 #[arg(long, env = "ROBONIX_EXECUTOR_LISTEN")]
37 pub listen: Option<String>,
38
39 #[arg(long, env = "ROBONIX_EXECUTOR_PROVIDER_ID")]
41 pub id: Option<String>,
42
43 #[arg(long, env = "ROBONIX_CONFIG_PATH")]
45 pub config: Option<PathBuf>,
46
47 #[arg(long)]
51 pub log: Option<String>,
52
53 #[arg(long)]
58 pub config_json: Option<String>,
59}
60
61#[derive(Default, Deserialize)]
62struct FileConfig {
63 #[serde(default)]
64 atlas_endpoint: Option<String>,
65 #[serde(default)]
66 listen: Option<String>,
67 #[serde(default)]
68 id: Option<String>,
69}
70
71impl ExecutorConfig {
72 pub fn resolve(args: Args) -> Result<Self> {
73 let file_cfg: FileConfig = match &args.config {
74 Some(path) => load_yaml(path)?,
75 None => FileConfig::default(),
76 };
77 Ok(Self {
78 atlas_endpoint: args
79 .atlas
80 .or_else(env_atlas)
81 .or(file_cfg.atlas_endpoint)
82 .unwrap_or_else(|| DEFAULT_ATLAS_ENDPOINT.to_string()),
83 listen: args
84 .listen
85 .or(file_cfg.listen)
86 .unwrap_or_else(|| DEFAULT_LISTEN.to_string()),
87 id: args
88 .id
89 .or(file_cfg.id)
90 .unwrap_or_else(|| DEFAULT_EXECUTOR_PROVIDER_ID.to_string()),
91 })
92 }
93}
94
95fn env_atlas() -> Option<String> {
107 std::env::var("ROBONIX_ATLAS")
108 .ok()
109 .filter(|v| !v.is_empty())
110}
111
112fn load_yaml(path: &Path) -> Result<FileConfig> {
113 let raw = std::fs::read_to_string(path)
114 .with_context(|| format!("read executor config '{}'", path.display()))?;
115 serde_yaml::from_str(&raw)
116 .with_context(|| format!("parse executor config '{}'", path.display()))
117}