Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 6 additions & 37 deletions crates/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,6 @@ pub(crate) struct ServerArgs {
/// Upstream Anthropic base URL (e.g. https://api.anthropic.com)
#[arg(long, env = "NEMO_RELAY_ANTHROPIC_BASE_URL")]
pub(crate) anthropic_base_url: Option<String>,
/// Generic plugin configuration JSON for process-level gateway plugin activation.
#[arg(long, env = "NEMO_RELAY_PLUGIN_CONFIG")]
pub(crate) plugin_config: Option<String>,
}

impl ServerArgs {
Expand All @@ -210,7 +207,6 @@ impl ServerArgs {
self.bind.is_some()
|| self.openai_base_url.is_some()
|| self.anthropic_base_url.is_some()
|| self.plugin_config.is_some()
|| self.config.is_some()
}
}
Expand Down Expand Up @@ -267,8 +263,6 @@ pub(crate) struct RunCommand {
#[arg(long)]
pub(crate) session_metadata: Option<String>,
#[arg(long)]
pub(crate) plugin_config: Option<String>,
#[arg(long)]
pub(crate) dry_run: bool,
#[arg(long)]
pub(crate) print: bool,
Expand Down Expand Up @@ -440,8 +434,8 @@ pub(crate) fn resolve_server_config(args: &ServerArgs) -> Result<ResolvedConfig,
/// Resolves transparent `run` configuration and switches the gateway to an ephemeral bind address.
///
/// Explicit run arguments override inherited top-level server flags, which override shared config.
/// Session metadata and plugin config are parsed as JSON here so malformed CLI values fail before
/// the child agent is spawned.
/// Session metadata is parsed as JSON here so malformed CLI values fail before the child agent is
/// spawned.
pub(crate) fn resolve_run_config(
command: &RunCommand,
inherited: Option<&ServerArgs>,
Expand All @@ -452,16 +446,7 @@ pub(crate) fn resolve_run_config(
.or_else(|| inherited.and_then(|args| args.config.as_ref()));
let mut resolved = load_shared_config(config)?;
if let Some(args) = inherited {
// Run-subcommand plugin config has higher precedence than inherited top-level plugin
// config. Skip only that inherited field so file/plugins.toml conflicts are still caught
// when the run-level override is applied below.
if command.plugin_config.is_some() && args.plugin_config.is_some() {
let mut inherited = args.clone();
inherited.plugin_config = None;
apply_server_overrides(&mut resolved.gateway, &inherited)?;
} else {
apply_server_overrides(&mut resolved.gateway, args)?;
}
apply_server_overrides(&mut resolved.gateway, args)?;
}
apply_run_overrides(&mut resolved.gateway, command)?;
resolved.gateway.bind = "127.0.0.1:0"
Expand All @@ -471,7 +456,7 @@ pub(crate) fn resolve_run_config(
}

// Applies subcommand-specific `run` overrides after inherited top-level flags. JSON-bearing fields
// are parsed here so invalid metadata or plugin config fails before the gateway binds a port.
// are parsed here so invalid session metadata fails before the gateway binds a port.
fn apply_run_overrides(config: &mut GatewayConfig, command: &RunCommand) -> Result<(), CliError> {
apply_run_url_overrides(config, command);
apply_run_json_overrides(config, command)?;
Expand All @@ -489,18 +474,15 @@ fn apply_run_url_overrides(config: &mut GatewayConfig, command: &RunCommand) {
}
}

// Parses JSON-bearing run overrides after simple values. Invalid metadata or plugin config fails
// before transparent run mode binds its ephemeral gateway listener.
// Parses JSON-bearing run overrides after simple values. Invalid session metadata fails before
// transparent run mode binds its ephemeral gateway listener.
fn apply_run_json_overrides(
config: &mut GatewayConfig,
command: &RunCommand,
) -> Result<(), CliError> {
if let Some(value) = &command.session_metadata {
config.metadata = Some(parse_json_option("session metadata", value)?);
}
if let Some(value) = &command.plugin_config {
apply_cli_plugin_config(config, value)?;
}
Ok(())
}

Expand All @@ -516,9 +498,6 @@ fn apply_server_overrides(config: &mut GatewayConfig, args: &ServerArgs) -> Resu
if let Some(value) = &args.anthropic_base_url {
config.anthropic_base_url = value.clone();
}
if let Some(value) = &args.plugin_config {
apply_cli_plugin_config(config, value)?;
}
Ok(())
}

Expand Down Expand Up @@ -790,16 +769,6 @@ fn apply_plugin_toml_config(
Ok(())
}

fn apply_cli_plugin_config(config: &mut GatewayConfig, value: &str) -> Result<(), CliError> {
if config.plugin_config.is_some() {
return Err(CliError::Config(
"plugin config is defined by both --plugin-config and file configuration; choose one source".into(),
));
}
config.plugin_config = Some(parse_json_option("plugin config", value)?);
Ok(())
}

// Applies configured agent commands and Cursor's temporary-hook behavior. Cursor's
// `patch_restore_hooks` flag is intentionally tri-state in file config so omitted values preserve
// the safe default while explicit `false` disables temporary hook mutation.
Expand Down
1 change: 0 additions & 1 deletion crates/cli/src/launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ pub(crate) async fn easy_path(
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: command.command,
Expand Down
67 changes: 0 additions & 67 deletions crates/cli/tests/coverage/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ command = "hermes --yolo chat"
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec![],
Expand Down Expand Up @@ -179,7 +178,6 @@ fn legacy_observability_config_sections_fail_clearly() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec![],
Expand Down Expand Up @@ -232,7 +230,6 @@ mode = "overwrite"
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec!["codex".into()],
Expand Down Expand Up @@ -521,30 +518,6 @@ config = { version = 1, components = [] }
assert!(error.contains("plugins.toml"));
}

#[test]
fn cli_plugin_config_conflicts_with_file_plugin_config() {
let temp = tempfile::tempdir().unwrap();
let config_path = temp.path().join("config.toml");
std::fs::write(&config_path, "").unwrap();
std::fs::write(temp.path().join("plugins.toml"), "version = 1\n").unwrap();
let command = RunCommand {
agent: Some(CodingAgent::Codex),
config: Some(config_path),
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: Some(r#"{"version":1,"components":[]}"#.into()),
dry_run: false,
print: false,
command: vec!["codex".into()],
};

let error = resolve_run_config(&command, None).unwrap_err().to_string();

assert!(error.contains("--plugin-config"));
assert!(error.contains("file configuration"));
}

#[test]
fn cli_run_overrides_config_values() {
let temp = tempfile::tempdir().unwrap();
Expand All @@ -563,7 +536,6 @@ openai_base_url = "http://file-openai"
openai_base_url: Some("http://cli-openai".into()),
anthropic_base_url: None,
session_metadata: Some(r#"{"team":"cli"}"#.into()),
plugin_config: None,
dry_run: false,
print: false,
command: vec!["codex".into()],
Expand Down Expand Up @@ -598,7 +570,6 @@ openai_base_url = "http://file-openai"
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec!["codex".into()],
Expand All @@ -609,34 +580,6 @@ openai_base_url = "http://file-openai"
assert_eq!(resolved.gateway.openai_base_url, "http://top-level-openai");
}

#[test]
fn run_plugin_config_overrides_inherited_top_level_plugin_config() {
let temp = tempfile::tempdir().unwrap();
let server = ServerArgs {
config: Some(isolated_config_path(&temp)),
plugin_config: Some(r#"{"components":["top-level"]}"#.into()),
..ServerArgs::default()
};
let command = RunCommand {
agent: Some(CodingAgent::Codex),
config: None,
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: Some(r#"{"components":["run"]}"#.into()),
dry_run: false,
print: false,
command: vec!["codex".into()],
};

let resolved = resolve_run_config(&command, Some(&server)).unwrap();

assert_eq!(
resolved.gateway.plugin_config,
Some(json!({ "components": ["run"] }))
);
}

#[test]
fn server_resolution_applies_all_server_overrides() {
let temp = tempfile::tempdir().unwrap();
Expand All @@ -645,18 +588,13 @@ fn server_resolution_applies_all_server_overrides() {
bind: Some("127.0.0.1:0".parse().unwrap()),
openai_base_url: Some("http://cli-openai".into()),
anthropic_base_url: Some("http://cli-anthropic".into()),
plugin_config: Some(r#"{"version":1,"components":[]}"#.into()),
};

let resolved = resolve_server_config(&args).unwrap();

assert_eq!(resolved.gateway.bind.to_string(), "127.0.0.1:0");
assert_eq!(resolved.gateway.openai_base_url, "http://cli-openai");
assert_eq!(resolved.gateway.anthropic_base_url, "http://cli-anthropic");
assert_eq!(
resolved.gateway.plugin_config,
Some(json!({ "version": 1, "components": [] }))
);
assert!(args.requested_daemon_mode());
}

Expand All @@ -669,7 +607,6 @@ fn run_resolution_applies_all_run_overrides() {
openai_base_url: Some("http://run-openai".into()),
anthropic_base_url: Some("http://run-anthropic".into()),
session_metadata: Some(r#"{"team":"run"}"#.into()),
plugin_config: Some(r#"{"components":["x"]}"#.into()),
dry_run: false,
print: false,
command: vec!["codex".into()],
Expand All @@ -680,10 +617,6 @@ fn run_resolution_applies_all_run_overrides() {
assert_eq!(resolved.gateway.openai_base_url, "http://run-openai");
assert_eq!(resolved.gateway.anthropic_base_url, "http://run-anthropic");
assert_eq!(resolved.gateway.metadata, Some(json!({ "team": "run" })));
assert_eq!(
resolved.gateway.plugin_config,
Some(json!({ "components": ["x"] }))
);
}

#[test]
Expand Down
9 changes: 0 additions & 9 deletions crates/cli/tests/coverage/launcher_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ fn infers_agent_from_command_or_uses_override() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec!["/usr/bin/codex".into()],
Expand Down Expand Up @@ -51,7 +50,6 @@ fn uses_configured_command_when_no_argv_is_supplied() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec![],
Expand All @@ -78,7 +76,6 @@ fn uses_configured_hermes_command_when_no_argv_is_supplied() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec![],
Expand All @@ -98,7 +95,6 @@ fn inference_failure_has_actionable_message() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec!["my-agent".into()],
Expand All @@ -123,7 +119,6 @@ fn missing_command_without_agent_errors() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec![],
Expand All @@ -146,7 +141,6 @@ fn agent_without_configured_command_falls_back_to_default_binary() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec![],
Expand All @@ -167,7 +161,6 @@ fn agent_with_passthrough_args_appends_to_configured_command() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: vec!["--model".into(), "openai/openai/gpt-5.1-codex".into()],
Expand Down Expand Up @@ -644,7 +637,6 @@ async fn run_starts_gateway_injects_env_and_returns_agent_exit_code() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: false,
print: false,
command: command_argv,
Expand Down Expand Up @@ -685,7 +677,6 @@ async fn dry_run_does_not_spawn_agent() {
openai_base_url: None,
anthropic_base_url: None,
session_metadata: None,
plugin_config: None,
dry_run: true,
print: false,
command: vec!["/path/that/does/not/exist".into()],
Expand Down
7 changes: 2 additions & 5 deletions docs/build-plugins/plugin-configuration-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,14 @@ The gateway reads only files named `plugins.toml`.

## Discovery

The gateway can receive plugin configuration from three source classes:
The gateway can receive plugin configuration from two source classes:

| Source | Use case |
|---|---|
| `plugins.toml` | Normal operator- and project-managed gateway plugin configuration. |
| `[plugins].config` in `config.toml` | Inline gateway config for small or generated setups. |
| `--plugin-config '<json>'` | CI, tests, wrappers, or one-off automation. |

Use only one source class for a given gateway run. The gateway fails clearly if
file-based plugin config and `--plugin-config` are both present, or if
`plugins.toml` and `[plugins].config` are both present.

When `--config path/to/config.toml` is supplied, plugin file discovery is scoped
Expand Down Expand Up @@ -253,8 +251,7 @@ installed by the plugin system.

Keep long-lived plugin setup in `plugins.toml`. Use `[plugins].config` in
`config.toml` only when a generated or embedded config must keep all gateway
settings in one file. Use `--plugin-config` for automation that should not write
files.
settings in one file.

Legacy observability config sections in `config.toml`, such as `[exporters]`,
`[observability]`, and `[export.openinference]`, are not supported. Configure
Expand Down
Loading