feat: v0.7.0 — AgentDef provider/model + install --provider/--model/--no-config : résolution flag > agent > défaut, erreur si provider inconnu (liste du registre), warning si modèle absent, résolution affichée en résumé et au --dry-run (closes #90)
- Providers réels livrés dans le catalogue par défaut (anthropic★/openai/deepseek/google) + hints provider sur claude-code/deepseek-harness - Suppression par null dans l'overlay : am providers remove écrit <nom>: null (merge traite null comme suppression, set_path remplace le null avant de descendre) - Cascade @secret (#89) : le provider déclaré de l'agent prime sur default_provider
This commit is contained in:
+112
-2
@@ -8,7 +8,15 @@ use crate::runner::SystemRunner;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub fn run(app: &App, agent_name: &str, method_sel: Option<&str>, force: bool) -> Result<i32> {
|
||||
pub fn run(
|
||||
app: &App,
|
||||
agent_name: &str,
|
||||
method_sel: Option<&str>,
|
||||
force: bool,
|
||||
provider: Option<&str>,
|
||||
model: Option<&str>,
|
||||
no_config: bool,
|
||||
) -> Result<i32> {
|
||||
let agent = require_agent(app, agent_name)?;
|
||||
let runner = SystemRunner::new(app.dry_run(), app.cli.verbose, &app.log);
|
||||
|
||||
@@ -38,6 +46,20 @@ pub fn run(app: &App, agent_name: &str, method_sel: Option<&str>, force: bool) -
|
||||
let methods = spec.all_methods();
|
||||
let method = select_method(&methods, method_sel)?;
|
||||
|
||||
// Provider/model resolution (issue #90): flag > agent.provider >
|
||||
// settings.default_provider ; flag --model > agent.model >
|
||||
// provider.default_model. Displayed in the summary (and by --dry-run).
|
||||
let resolved = resolve_provider_config(app, agent, provider, model)?;
|
||||
if no_config {
|
||||
app.log
|
||||
.info(crate::i18n::tr("--no-config : aucune configuration post-install"));
|
||||
} else if !spec.configurable {
|
||||
app.log.info(&crate::tr_fmt!(
|
||||
"{} ne prend aucune configuration (install.configurable: false)",
|
||||
agent.name
|
||||
));
|
||||
}
|
||||
|
||||
// Already installed?
|
||||
let existing = app.state.get(&agent.name).ok().flatten();
|
||||
if existing.is_some() && !force && !app.dry_run() {
|
||||
@@ -138,10 +160,17 @@ pub fn run(app: &App, agent_name: &str, method_sel: Option<&str>, force: bool) -
|
||||
let entry = installers::make_entry(agent, method, &outcome, app);
|
||||
let version = entry.version.clone().unwrap_or_else(|| "unknown".to_string());
|
||||
app.state.set(&entry)?;
|
||||
let mut args = vec![format!("method={}", method.kind)];
|
||||
if let Some((p, m)) = &resolved {
|
||||
args.push(format!("provider={p}"));
|
||||
if let Some(m) = m {
|
||||
args.push(format!("model={m}"));
|
||||
}
|
||||
}
|
||||
app.emit(
|
||||
&Event::now(EventKind::Install)
|
||||
.with_agent(agent.name.clone())
|
||||
.with_args(vec![format!("method={}", method.kind)]),
|
||||
.with_args(args),
|
||||
);
|
||||
crate::hooks::run_hooks(
|
||||
app,
|
||||
@@ -161,3 +190,84 @@ pub fn run(app: &App, agent_name: &str, method_sel: Option<&str>, force: bool) -
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Resolve the provider + model of this install (issue #90) and print what
|
||||
/// will be configured. Returns (provider, model) when resolved.
|
||||
///
|
||||
/// Validation rules:
|
||||
/// - an explicit `--provider` that is not registered → error listing the
|
||||
/// registry (the user asked for it by name);
|
||||
/// - an agent-declared provider that is not registered → warning only
|
||||
/// (catalog metadata may reference a provider the user has not added yet);
|
||||
/// - a model absent from the provider's model list → warning, not blocking.
|
||||
fn resolve_provider_config(
|
||||
app: &App,
|
||||
agent: &AgentDef,
|
||||
flag_provider: Option<&str>,
|
||||
flag_model: Option<&str>,
|
||||
) -> Result<Option<(String, Option<String>)>> {
|
||||
let flag_p = flag_provider.filter(|p| !p.is_empty());
|
||||
let agent_p = agent.provider.as_deref().filter(|p| !p.is_empty());
|
||||
|
||||
// Explicit provider requested but not registered: hard error.
|
||||
if let Some(p) = flag_p {
|
||||
if crate::providers::get(&app.config, p).is_none() {
|
||||
let known = crate::providers::names(&app.config);
|
||||
let hint = if known.is_empty() {
|
||||
"am providers add <nom> --base-url <url>".to_string()
|
||||
} else {
|
||||
known.join(", ")
|
||||
};
|
||||
bail!(crate::tr_fmt!(
|
||||
"provider '{}' inconnu — providers enregistrés: {}",
|
||||
p,
|
||||
hint
|
||||
));
|
||||
}
|
||||
}
|
||||
// Agent-declared provider not registered: warn (unless the user already
|
||||
// overrode it with --provider, which was validated above).
|
||||
if let Some(p) = agent_p {
|
||||
if flag_p.is_none() && crate::providers::get(&app.config, p).is_none() {
|
||||
app.log.warn(&crate::tr_fmt!(
|
||||
"provider '{}' (déclaré par {}) non enregistré — voir: am providers add {} --base-url <url>",
|
||||
p,
|
||||
agent.name,
|
||||
p
|
||||
));
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
let Some((pname, def, model)) = crate::providers::resolve_for(
|
||||
&app.config,
|
||||
agent.provider.as_deref(),
|
||||
agent.model.as_deref(),
|
||||
flag_provider,
|
||||
flag_model,
|
||||
) else {
|
||||
app.log.info(crate::i18n::tr(
|
||||
"aucun provider configuré — voir: am providers add <nom> --base-url <url>",
|
||||
));
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Model not in the provider's list: warn, keep going.
|
||||
if let Some(m) = model {
|
||||
if !def.models.is_empty() && !def.models.iter().any(|x| x == m) {
|
||||
app.log.warn(&crate::tr_fmt!(
|
||||
"modèle '{}' absent de la liste du provider '{}' — modèles: {}",
|
||||
m,
|
||||
pname,
|
||||
def.models.join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
let model_label = model.unwrap_or("-");
|
||||
app.log.info(&crate::tr_fmt!(
|
||||
"configuré avec le provider {} (modèle {})",
|
||||
pname,
|
||||
model_label
|
||||
));
|
||||
Ok(Some((pname.to_string(), model.map(String::from))))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user