feat: v0.7.0 — am run/start --provider/--model : résolution cloud via le registre (base_url + keyring + modèle), fallback cloud quand le modèle local est inconnu, canal model_env/model_arg/env_map, jamais de token en clair (closes #92)
- apply_provider : providers::resolve_for (flag > agent > défaut) + apply_env_map_resolved (api_key = @secret, base_url/model du provider résolu) - start/run/restart : --provider et --model dans le CLI, le REPL et la complétion ; specs help + man pages - Tests unitaires : résolution, cascade, canal déclaré, erreur provider inconnu (liste), absence de fuite (env sans valeur sk-), fallback embarqué
This commit is contained in:
+1
-1
@@ -501,7 +501,7 @@ S'appuie sur #36 (secrets), #40 (profils), #71 (modèle), #66 (sync) — livrés
|
||||
| [#89](https://git.dracodev.net/Projets/agent-manager/issues/89) | ✅ Secrets partagés par provider (namespace keyring + résolution `@secret` fallback) | S | #88 |
|
||||
| [#90](https://git.dracodev.net/Projets/agent-manager/issues/90) | ✅ AgentDef provider/model + flags `--provider/--model/--no-config` à l'install | M | #88 |
|
||||
| [#91](https://git.dracodev.net/Projets/agent-manager/issues/91) | ✅ Configuration post-install : bloc `config:` par agent (env_map + fichiers) | L | #89, #90 |
|
||||
| [#92](https://git.dracodev.net/Projets/agent-manager/issues/92) | `am run/start --provider` : override au lancement (providers cloud inclus) | M | #90 |
|
||||
| [#92](https://git.dracodev.net/Projets/agent-manager/issues/92) | ✅ `am run/start --provider` : override au lancement (providers cloud inclus) | M | #90 |
|
||||
|
||||
Critère de sortie du jalon : un agent installé est opérationnel sans
|
||||
configuration manuelle (tokens au keyring, provider/modèle par défaut).
|
||||
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
.SH NAME
|
||||
restart \- Restart an agent: stop, then start with the same options
|
||||
.SH SYNOPSIS
|
||||
\fBrestart\fR [\fB\-b\fR|\fB\-\-background\fR] [\fB\-f\fR|\fB\-\-foreground\fR] [\fB\-\-args\fR] [\fB\-\-env\fR] [\fB\-\-notify\fR] [\fB\-\-profile\fR] [\fB\-\-model\fR] [\fB\-\-parallel\fR] [\fB\-\-container\fR] [\fB\-\-force\fR] [\fB\-\-timeout\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
|
||||
\fBrestart\fR [\fB\-b\fR|\fB\-\-background\fR] [\fB\-f\fR|\fB\-\-foreground\fR] [\fB\-\-args\fR] [\fB\-\-env\fR] [\fB\-\-notify\fR] [\fB\-\-profile\fR] [\fB\-\-model\fR] [\fB\-\-provider\fR] [\fB\-\-parallel\fR] [\fB\-\-container\fR] [\fB\-\-force\fR] [\fB\-\-timeout\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
|
||||
.SH DESCRIPTION
|
||||
Restart an agent: stop, then start with the same options
|
||||
.SH OPTIONS
|
||||
@@ -30,6 +30,9 @@ Apply an environment profile (env + args, defined in the config)
|
||||
\fB\-\-model\fR \fI<MODEL>\fR
|
||||
Local model to use for this run (validated against the local runtimes)
|
||||
.TP
|
||||
\fB\-\-provider\fR \fI<PROVIDER>\fR
|
||||
Provider to use for this run (issue #92): registry lookup, base_url + keyring token + model
|
||||
.TP
|
||||
\fB\-\-parallel\fR
|
||||
Start every member of a group simultaneously (issue #57)
|
||||
.TP
|
||||
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
.SH NAME
|
||||
run \- Run the agent command directly with the given arguments (no process management)
|
||||
.SH SYNOPSIS
|
||||
\fBrun\fR [\fB\-\-model\fR] [\fB\-\-container\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR> [\fIARGS...\fR]
|
||||
\fBrun\fR [\fB\-\-model\fR] [\fB\-\-provider\fR] [\fB\-\-container\fR] [\fB\-h\fR|\fB\-\-help\fR] <\fIAGENT\fR> [\fIARGS...\fR]
|
||||
.SH DESCRIPTION
|
||||
Run the agent command directly with the given arguments (no process management)
|
||||
.SH OPTIONS
|
||||
@@ -12,6 +12,9 @@ Run the agent command directly with the given arguments (no process management)
|
||||
\fB\-\-model\fR \fI<MODEL>\fR
|
||||
Local model to use for this run (validated against the local runtimes)
|
||||
.TP
|
||||
\fB\-\-provider\fR \fI<PROVIDER>\fR
|
||||
Provider to use for this run (issue #92): registry lookup, base_url + keyring token + model
|
||||
.TP
|
||||
\fB\-\-container\fR
|
||||
Run the agent inside a container (issue #58)
|
||||
.TP
|
||||
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
.SH NAME
|
||||
start \- Start an agent (foreground by default, or detached with \-\-background)
|
||||
.SH SYNOPSIS
|
||||
\fBstart\fR [\fB\-b\fR|\fB\-\-background\fR] [\fB\-f\fR|\fB\-\-foreground\fR] [\fB\-\-args\fR] [\fB\-\-env\fR] [\fB\-\-notify\fR] [\fB\-\-profile\fR] [\fB\-\-model\fR] [\fB\-\-parallel\fR] [\fB\-\-container\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
|
||||
\fBstart\fR [\fB\-b\fR|\fB\-\-background\fR] [\fB\-f\fR|\fB\-\-foreground\fR] [\fB\-\-args\fR] [\fB\-\-env\fR] [\fB\-\-notify\fR] [\fB\-\-profile\fR] [\fB\-\-model\fR] [\fB\-\-provider\fR] [\fB\-\-parallel\fR] [\fB\-\-container\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIAGENT\fR]
|
||||
.SH DESCRIPTION
|
||||
Start an agent (foreground by default, or detached with \-\-background)
|
||||
.SH OPTIONS
|
||||
@@ -30,6 +30,9 @@ Apply an environment profile (env + args, defined in the config)
|
||||
\fB\-\-model\fR \fI<MODEL>\fR
|
||||
Local model to use for this run (validated against the local runtimes)
|
||||
.TP
|
||||
\fB\-\-provider\fR \fI<PROVIDER>\fR
|
||||
Provider to use for this run (issue #92): registry lookup, base_url + keyring token + model
|
||||
.TP
|
||||
\fB\-\-parallel\fR
|
||||
Start every member of a group simultaneously (issue #57)
|
||||
.TP
|
||||
|
||||
+26
-3
@@ -63,23 +63,46 @@ pub fn provider_pref(agent: &AgentDef) -> Option<&str> {
|
||||
/// model/base_url/provider -> values of the resolved provider. Explicit
|
||||
/// values already present in agent.env are never overwritten.
|
||||
pub fn apply_env_map(env: &mut BTreeMap<String, String>, agent: &AgentDef, config: &Config) {
|
||||
apply_env_map_resolved(env, agent, config, None, None);
|
||||
}
|
||||
|
||||
/// Same as `apply_env_map` but with the provider and model fixed by the
|
||||
/// caller (issue #92: `am run/start --provider/--model`): the flags win
|
||||
/// over the agent declaration and the settings default.
|
||||
pub fn apply_env_map_resolved(
|
||||
env: &mut BTreeMap<String, String>,
|
||||
agent: &AgentDef,
|
||||
config: &Config,
|
||||
provider_override: Option<&str>,
|
||||
model_override: Option<&str>,
|
||||
) {
|
||||
let Some(cfg) = &agent.config else {
|
||||
return;
|
||||
};
|
||||
if cfg.env_map.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(pname) = provider_pref(agent).or_else(|| crate::providers::default_name(config)) else {
|
||||
let Some(pname) = provider_override
|
||||
.filter(|p| !p.is_empty())
|
||||
.or_else(|| provider_pref(agent))
|
||||
.or_else(|| crate::providers::default_name(config))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(def) = crate::providers::get(config, pname) else {
|
||||
return;
|
||||
};
|
||||
let model = agent.model.as_deref().or(def.default_model.as_deref());
|
||||
let model = model_override
|
||||
.filter(|m| !m.is_empty())
|
||||
.or(agent.model.as_deref())
|
||||
.or(def.default_model.as_deref());
|
||||
for (slot, var) in &cfg.env_map {
|
||||
let value = match slot.as_str() {
|
||||
"api_key" => "@secret".to_string(),
|
||||
"model" => model.unwrap_or_default().to_string(),
|
||||
"model" => {
|
||||
let Some(m) = model else { continue };
|
||||
m.to_string()
|
||||
}
|
||||
"base_url" => def.base_url.clone(),
|
||||
"provider" => pname.to_string(),
|
||||
_ => continue, // unknown slots are ignored
|
||||
|
||||
@@ -495,6 +495,9 @@ pub enum Command {
|
||||
/// Local model to use for this run (validated against the local runtimes)
|
||||
#[arg(long, value_name = "MODEL")]
|
||||
model: Option<String>,
|
||||
/// Provider to use for this run (issue #92): registry lookup, base_url + keyring token + model
|
||||
#[arg(long, value_name = "PROVIDER")]
|
||||
provider: Option<String>,
|
||||
/// Run the agent inside a container (issue #58)
|
||||
#[arg(long)]
|
||||
container: bool,
|
||||
@@ -566,6 +569,9 @@ pub struct StartArgs {
|
||||
/// Local model to use for this run (validated against the local runtimes)
|
||||
#[arg(long, value_name = "MODEL")]
|
||||
pub model: Option<String>,
|
||||
/// Provider to use for this run (issue #92): registry lookup, base_url + keyring token + model
|
||||
#[arg(long, value_name = "PROVIDER")]
|
||||
pub provider: Option<String>,
|
||||
/// Start every member of a group simultaneously (issue #57)
|
||||
#[arg(long, action = ArgAction::SetTrue)]
|
||||
pub parallel: bool,
|
||||
|
||||
+9
-2
@@ -226,8 +226,15 @@ pub fn execute_command(app: &App, cmd: &Command) -> Result<i32> {
|
||||
Command::Doctor { fix, watch } => {
|
||||
doctor_cmd::run(app, *fix, *watch)
|
||||
}
|
||||
Command::Run { agent, model, container, args } => {
|
||||
run_cmd::run(app, agent, model.as_deref(), *container, args)
|
||||
Command::Run { agent, model, provider, container, args } => {
|
||||
run_cmd::run(
|
||||
app,
|
||||
agent,
|
||||
model.as_deref(),
|
||||
provider.as_deref(),
|
||||
*container,
|
||||
args,
|
||||
)
|
||||
}
|
||||
Command::Service(svc) => match svc {
|
||||
crate::cli::ServiceCmd::Install { agent, autostart } => {
|
||||
|
||||
+242
-4
@@ -71,13 +71,30 @@ pub fn start_with_cwd(app: &App, opts: &StartArgs, cwd: Option<&std::path::Path>
|
||||
)?;
|
||||
// --model (issue #71): resolve against the local runtimes, then
|
||||
// transmit through the agent's declared channel (env var or flag).
|
||||
// --provider (issue #92): resolve from the LLM provider registry.
|
||||
let mut extra_args = extra_args;
|
||||
let mut extra_env = extra_env;
|
||||
if let Some(m) = opts.model.as_deref() {
|
||||
if let Some(p) = opts.provider.as_deref() {
|
||||
let agent = require_agent(app, &target)?;
|
||||
let (a, e) = apply_model(app, agent, m)?;
|
||||
let (a, e) = apply_provider(app, agent, Some(p), opts.model.as_deref())?
|
||||
.expect("a provider flag was given: apply_provider always resolves or errors");
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
} else if let Some(m) = opts.model.as_deref() {
|
||||
let agent = require_agent(app, &target)?;
|
||||
match apply_model(app, agent, m) {
|
||||
Ok((a, e)) => {
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
}
|
||||
Err(local_err) => match apply_provider(app, agent, None, Some(m))? {
|
||||
Some((a, e)) => {
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
}
|
||||
None => return Err(local_err),
|
||||
},
|
||||
}
|
||||
}
|
||||
if let Some(group) = crate::catalog::Catalog::parse_group_selector(&target) {
|
||||
let members = app.catalog.group_members(group);
|
||||
@@ -320,6 +337,7 @@ pub fn run(
|
||||
app: &App,
|
||||
target: &str,
|
||||
model: Option<&str>,
|
||||
provider: Option<&str>,
|
||||
container: bool,
|
||||
extra: &[OsString],
|
||||
) -> Result<i32> {
|
||||
@@ -333,10 +351,28 @@ pub fn run(
|
||||
.map(|o| o.to_string_lossy().to_string())
|
||||
.collect();
|
||||
let mut extra_env: BTreeMap<String, String> = BTreeMap::new();
|
||||
if let Some(m) = model {
|
||||
let (a, e) = apply_model(app, agent, m)?;
|
||||
// Issue #92: --provider resolves from the LLM provider registry (cloud);
|
||||
// --model alone keeps the local-runtimes-first behavior (issue #71) with
|
||||
// a cloud fallback when the model is unknown locally.
|
||||
if let Some(p) = provider {
|
||||
let (a, e) = apply_provider(app, agent, Some(p), model)?
|
||||
.expect("a provider flag was given: apply_provider always resolves or errors");
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
} else if let Some(m) = model {
|
||||
match apply_model(app, agent, m) {
|
||||
Ok((a, e)) => {
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
}
|
||||
Err(local_err) => match apply_provider(app, agent, None, Some(m))? {
|
||||
Some((a, e)) => {
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
}
|
||||
None => return Err(local_err),
|
||||
},
|
||||
}
|
||||
}
|
||||
let exec = resolve_exec(app, agent, &extra_args, &extra_env)?;
|
||||
if app.dry_run() {
|
||||
@@ -474,6 +510,70 @@ pub fn apply_model(
|
||||
Ok((args, env))
|
||||
}
|
||||
|
||||
/// Issue #92: resolve `--provider`/`--model` against the LLM provider
|
||||
/// registry (cloud) and transmit through the agent's declared channels:
|
||||
/// `model_env` / `model_arg`, or the `config.env_map` slots (issue #91).
|
||||
/// The token travels as the `@secret` reference resolved by
|
||||
/// resolve_env_secrets — never on the command line. Returns None when no
|
||||
/// provider is configured at all (the caller falls back to the
|
||||
/// local-runtime path).
|
||||
fn apply_provider(
|
||||
app: &App,
|
||||
agent: &AgentDef,
|
||||
provider: Option<&str>,
|
||||
model: Option<&str>,
|
||||
) -> Result<Option<(Vec<String>, BTreeMap<String, String>)>> {
|
||||
let Some((pname, _def, resolved_model)) = crate::providers::resolve_for(
|
||||
&app.config,
|
||||
crate::agent_config::provider_pref(agent),
|
||||
agent.model.as_deref(),
|
||||
provider,
|
||||
model,
|
||||
) else {
|
||||
if provider.is_some() {
|
||||
let known = crate::providers::names(&app.config);
|
||||
let hint = if known.is_empty() {
|
||||
"aucun (am providers add <nom> --base-url <url>)".to_string()
|
||||
} else {
|
||||
known.join(", ")
|
||||
};
|
||||
bail!(crate::tr_fmt!(
|
||||
"provider '{}' inconnu — providers enregistrés: {}",
|
||||
provider.unwrap_or(""),
|
||||
hint
|
||||
));
|
||||
}
|
||||
return Ok(None);
|
||||
};
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
let mut env: BTreeMap<String, String> = BTreeMap::new();
|
||||
// env_map resolved against THIS provider (api_key stays @secret).
|
||||
crate::agent_config::apply_env_map_resolved(
|
||||
&mut env,
|
||||
agent,
|
||||
&app.config,
|
||||
Some(pname),
|
||||
resolved_model,
|
||||
);
|
||||
// The model through the agent's declared channel (model_env > model_arg);
|
||||
// otherwise the env_map "model" slot already carried it.
|
||||
if let Some(m) = resolved_model {
|
||||
if let Some(var) = &agent.model_env {
|
||||
env.insert(var.clone(), m.to_string());
|
||||
} else if let Some(flag) = &agent.model_arg {
|
||||
args.push(flag.clone());
|
||||
args.push(m.to_string());
|
||||
}
|
||||
}
|
||||
app.log.info(&crate::tr_fmt!(
|
||||
"provider {} (modèle {}) au lancement de {}",
|
||||
pname,
|
||||
resolved_model.unwrap_or("-"),
|
||||
agent.name
|
||||
));
|
||||
Ok(Some((args, env)))
|
||||
}
|
||||
|
||||
/// Seconds elapsed since an RFC 3339 timestamp (None when unparsable).
|
||||
fn seconds_since(ts: Option<&str>) -> Option<u64> {
|
||||
let t = chrono::DateTime::parse_from_rfc3339(ts?).ok()?;
|
||||
@@ -483,3 +583,141 @@ fn seconds_since(ts: Option<&str>) -> Option<u64> {
|
||||
.num_seconds();
|
||||
Some(secs.max(0) as u64)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::{AgentConfig, AgentDef, Config};
|
||||
use clap::Parser;
|
||||
|
||||
fn test_app(providers_yaml: &str) -> App {
|
||||
let guard = tempfile::tempdir().unwrap();
|
||||
let dir = guard.path().to_path_buf();
|
||||
std::mem::forget(guard);
|
||||
let cfg = dir.join("config.yaml");
|
||||
std::fs::write(
|
||||
&cfg,
|
||||
format!(
|
||||
"version: \"1.0\"\nsettings:\n auto_install_deps: false\n confirm_before_run: false\n{providers_yaml}agents: []\n"
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
|
||||
crate::app::App::from_cli(cli).unwrap()
|
||||
}
|
||||
|
||||
fn agent(model_env: bool, model_arg: bool) -> AgentDef {
|
||||
let mut env_map = BTreeMap::new();
|
||||
env_map.insert("api_key".to_string(), "MY_KEY".to_string());
|
||||
env_map.insert("model".to_string(), "MY_MODEL".to_string());
|
||||
env_map.insert("base_url".to_string(), "MY_URL".to_string());
|
||||
AgentDef {
|
||||
name: "demo".to_string(),
|
||||
display_name: None,
|
||||
description: None,
|
||||
category: None,
|
||||
website: None,
|
||||
install: None,
|
||||
dependencies: vec![],
|
||||
run: Some("demo".to_string()),
|
||||
args: vec![],
|
||||
env: BTreeMap::new(),
|
||||
version: None,
|
||||
pin_version: None,
|
||||
model_env: model_env.then(|| "MY_MODEL".to_string()),
|
||||
model_arg: model_arg.then(|| "--model".to_string()),
|
||||
provider: None,
|
||||
model: None,
|
||||
config: Some(AgentConfig {
|
||||
env_map,
|
||||
files: vec![],
|
||||
provider_default: None,
|
||||
}),
|
||||
tags: vec![],
|
||||
installable: false,
|
||||
note: None,
|
||||
hidden: false,
|
||||
platforms: vec![],
|
||||
healthcheck: None,
|
||||
container: None,
|
||||
cost_model: None,
|
||||
}
|
||||
}
|
||||
|
||||
const OPENAI: &str = " default_provider: openai\n providers:\n openai:\n base_url: https://api.openai.com/v1\n default_model: gpt-5.2\n models: [gpt-5.2]\n";
|
||||
|
||||
#[test]
|
||||
fn apply_provider_resolves_and_never_leaks_the_token() {
|
||||
let app = test_app(OPENAI);
|
||||
let (args, env) = apply_provider(&app, &agent(false, false), Some("openai"), Some("gpt-5.2"))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
// The token slot is the @secret reference — never a value.
|
||||
assert_eq!(env.get("MY_KEY").unwrap(), "@secret");
|
||||
assert_eq!(env.get("MY_MODEL").unwrap(), "gpt-5.2");
|
||||
assert_eq!(env.get("MY_URL").unwrap(), "https://api.openai.com/v1");
|
||||
assert!(!env.values().any(|v| v.contains("sk-")), "{env:?}");
|
||||
assert!(args.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_provider_uses_the_declared_model_channel() {
|
||||
let app = test_app(OPENAI);
|
||||
// model_env wins over the env_map slot.
|
||||
let (args, env) = apply_provider(&app, &agent(true, false), Some("openai"), None)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(env.get("MY_MODEL").unwrap(), "gpt-5.2");
|
||||
assert!(args.is_empty());
|
||||
// model_arg is pushed on the command line.
|
||||
let (args, _) = apply_provider(&app, &agent(false, true), Some("openai"), Some("gpt-5.2"))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(args, vec!["--model".to_string(), "gpt-5.2".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_provider_unknown_is_an_error_listing_the_registry() {
|
||||
let app = test_app(OPENAI);
|
||||
let err = apply_provider(&app, &agent(false, false), Some("nope"), None).unwrap_err();
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("nope"), "{msg}");
|
||||
assert!(msg.contains("openai"), "registry list missing: {msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_provider_falls_back_to_the_embedded_default_provider() {
|
||||
// The embedded default catalog ships providers (anthropic★ since
|
||||
// issue #90): even an empty user config resolves through the cloud.
|
||||
let app = test_app("");
|
||||
let (_, env) = apply_provider(&app, &agent(false, false), None, Some("x"))
|
||||
.unwrap()
|
||||
.expect("the embedded default provider resolves");
|
||||
// Model "x" is unknown: the env_map model slot is skipped, but the
|
||||
// provider slot is wired.
|
||||
assert_eq!(env.get("MY_URL").unwrap(), "https://api.anthropic.com/v1");
|
||||
assert_eq!(env.get("MY_KEY").unwrap(), "@secret");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_flag_falls_back_to_the_cloud_registry() {
|
||||
let app = test_app(OPENAI);
|
||||
// apply_model bails (no local runtime in the test env) → the caller
|
||||
// falls back to apply_provider with the default provider.
|
||||
let local = apply_model(&app, &agent(false, false), "gpt-5.2").unwrap_err();
|
||||
assert!(local.to_string().contains("not found on any local runtime"));
|
||||
let (_, env) = apply_provider(&app, &agent(false, false), None, Some("gpt-5.2"))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(env.get("MY_MODEL").unwrap(), "gpt-5.2");
|
||||
assert_eq!(env.get("MY_URL").unwrap(), "https://api.openai.com/v1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_parses_the_provider_registry_and_agents() {
|
||||
let _cfg: Config = serde_yaml::from_str(&format!(
|
||||
"version: \"1.0\"\nsettings:\n{OPENAI}agents: []\n"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -156,6 +156,18 @@ const START_FLAGS: &[HelpFlag] = &[
|
||||
value: "",
|
||||
desc: "Run inside the agent's container profile (issue #58)",
|
||||
},
|
||||
HelpFlag {
|
||||
short: "",
|
||||
long: "--model",
|
||||
value: "MODEL",
|
||||
desc: "Model to use: local runtime first, then the provider registry (issue #71/#92)",
|
||||
},
|
||||
HelpFlag {
|
||||
short: "",
|
||||
long: "--provider",
|
||||
value: "PROVIDER",
|
||||
desc: "Provider of the registry to use (base_url + keyring token + model) (issue #92)",
|
||||
},
|
||||
];
|
||||
|
||||
pub static HELP_SPECS: &[HelpSpec] = &[
|
||||
@@ -911,7 +923,8 @@ pub static HELP_SPECS: &[HelpSpec] = &[
|
||||
about: "Run the agent command directly with the given arguments (no process management).",
|
||||
search_terms: &["exec", "pass-through", "container", "docker", "podman"],
|
||||
flags: &[
|
||||
HelpFlag { short: "", long: "--model", value: "MODEL", desc: "Local model to use (issue #71)" },
|
||||
HelpFlag { short: "", long: "--model", value: "MODEL", desc: "Model to use: local runtime first, then the provider registry (issue #71/#92)" },
|
||||
HelpFlag { short: "", long: "--provider", value: "PROVIDER", desc: "Provider of the registry to use (base_url + keyring token + model) (issue #92)" },
|
||||
HelpFlag { short: "", long: "--container", value: "", desc: "Run inside the agent's container profile (issue #58)" },
|
||||
],
|
||||
subcommands: &[],
|
||||
@@ -922,6 +935,7 @@ pub static HELP_SPECS: &[HelpSpec] = &[
|
||||
io: None,
|
||||
examples: &[
|
||||
HelpExample { desc: "Run an agent command directly.", code: "run claude-code" },
|
||||
HelpExample { desc: "Use a provider of the registry.", code: "run claude-code --provider anthropic --model claude-sonnet-4" },
|
||||
HelpExample { desc: "Pass --help through to the agent (after --).", code: "run claude-code -- --help" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -185,6 +185,7 @@ pub const CATALOG: &[(&str, &str)] = &[
|
||||
("aucun provider configuré — voir: am providers add <nom> --base-url <url>", "no provider configured — see: am providers add <name> --base-url <url>"),
|
||||
("modèle '{}' absent de la liste du provider '{}' — modèles: {}", "model '{}' is not in provider '{}' model list — models: {}"),
|
||||
("configuré avec le provider {} (modèle {})", "configured with provider {} (model {})"),
|
||||
("provider {} (modèle {}) au lancement de {}", "provider {} (model {}) when launching {}"),
|
||||
("agent sans adaptateur de config — voir sa doc", "agent without a config adapter — see its docs"),
|
||||
("config par variables d'env — injectée au démarrage", "env-var configuration — injected at start"),
|
||||
("aucun provider résolu — fichiers de config non écrits", "no provider resolved — config files not written"),
|
||||
|
||||
@@ -254,6 +254,7 @@ impl AmCompleter {
|
||||
"--category", "--args", "--env", "--notify", "--timeout", "--yes",
|
||||
"--verbose", "--quiet", "--json", "--dry-run", "--no-color",
|
||||
"--profile", "--installed", "--tag", "--output", "--random",
|
||||
"--model", "--provider", "--no-config",
|
||||
],
|
||||
agents,
|
||||
catalog_agents,
|
||||
@@ -1732,6 +1733,8 @@ fn handle_line(
|
||||
agent: rest.first().cloned(),
|
||||
background: flag("--background") || flag("-b"),
|
||||
profile: opt_value("--profile"),
|
||||
model: opt_value("--model"),
|
||||
provider: opt_value("--provider"),
|
||||
..Default::default()
|
||||
}),
|
||||
"stop" => Command::Stop {
|
||||
@@ -1744,6 +1747,8 @@ fn handle_line(
|
||||
agent: rest.first().cloned(),
|
||||
background: flag("--background") || flag("-b"),
|
||||
profile: opt_value("--profile"),
|
||||
model: opt_value("--model"),
|
||||
provider: opt_value("--provider"),
|
||||
..Default::default()
|
||||
},
|
||||
force: flag("--force"),
|
||||
@@ -1839,6 +1844,7 @@ fn handle_line(
|
||||
Command::Run {
|
||||
agent,
|
||||
model: opt_value("--model"),
|
||||
provider: opt_value("--provider"),
|
||||
container: flag("--container"),
|
||||
args: rest[1..].iter().map(|s| s.as_str().into()).collect(),
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ agents:
|
||||
#[test]
|
||||
fn run_emits_one_run_event_with_exit_code() {
|
||||
let app = app_with_agent("run", &["run", "echo-agent"], ECHO_AGENT);
|
||||
let code = run_cmd::run(&app, "echo-agent", None, false, &[OsString::from("hello")]).unwrap();
|
||||
let code = run_cmd::run(&app, "echo-agent", None, None, false, &[OsString::from("hello")]).unwrap();
|
||||
assert_eq!(code, 0);
|
||||
let evs = events(&app);
|
||||
assert_eq!(evs.len(), 1, "exactly one event expected, got {:?}", evs);
|
||||
@@ -58,7 +58,7 @@ fn run_emits_one_run_event_with_exit_code() {
|
||||
#[test]
|
||||
fn dry_run_writes_no_event() {
|
||||
let app = app_with_agent("dry", &["run", "echo-agent", "--dry-run"], ECHO_AGENT);
|
||||
let code = run_cmd::run(&app, "echo-agent", None, false, &[]).unwrap();
|
||||
let code = run_cmd::run(&app, "echo-agent", None, None, false, &[]).unwrap();
|
||||
assert_eq!(code, 0);
|
||||
assert!(events(&app).is_empty(), "dry-run must not append events");
|
||||
assert!(events::journal_files(&app.events_dir()).is_empty());
|
||||
|
||||
Reference in New Issue
Block a user