Axe 11 (modèles) : - am models : inventaire des runtimes locaux (ollama list --format json, llama.cpp .gguf sur disque, LM Studio API locale) avec cache façon probe.rs (TTL 30s) et absence de runtime signalée sans erreur fatale - am run <agent> --model <m> : résolution dans l'inventaire, transmission via model_env/model_arg de l'agent (défaut AM_MODEL), tracé en session - am models --prune : candidats inutilisés depuis settings.models_prune_days (défaut 30), :latest toujours protégé, dry-run + confirmation + événement EventKind::Model journalisé Axes 7/9 (catalogue) : - am catalog update : fetch du catalogue officiel, diff (ajoutés/retirés/ modifiés), confirmation, enregistrement en include - am catalog add <url> : validation du format (parse + validate), cache catalog-cache.json (TTL 3600s), événement EventKind::Catalog journalisé - include par URL (http/https/file) dans config.yaml, résolu au chargement avec cache + TTL ; override local toujours prioritaire (issue #67) - am suggest <requête> : réutilise le scoring fuzzy du catalogue, boost par usage réel (lancements + durée depuis le journal), top 5 justifié, --json REPL : commandes models/catalog/suggest (parse, complétion, descriptions), help specs, tip cheat sheet, man pages régénérées (49 pages, dont 3 nouvelles). Version bump 0.4.5, ROADMAP cases cochées, 266 tests verts.
This commit is contained in:
+68
-3
@@ -69,6 +69,16 @@ pub fn start_with_cwd(app: &App, opts: &StartArgs, cwd: Option<&std::path::Path>
|
||||
&extra_args,
|
||||
&extra_env,
|
||||
)?;
|
||||
// --model (issue #71): resolve against the local runtimes, then
|
||||
// transmit through the agent's declared channel (env var or flag).
|
||||
let mut extra_args = extra_args;
|
||||
let mut extra_env = extra_env;
|
||||
if let Some(m) = opts.model.as_deref() {
|
||||
let agent = require_agent(app, &target)?;
|
||||
let (a, e) = apply_model(app, agent, m)?;
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
}
|
||||
if let Some(group) = crate::catalog::Catalog::parse_group_selector(&target) {
|
||||
let members = app.catalog.group_members(group);
|
||||
if members.is_empty() {
|
||||
@@ -299,10 +309,19 @@ pub fn restart(app: &App, opts: &StartArgs, force: bool, timeout: Option<u64>) -
|
||||
}
|
||||
|
||||
/// run: execute the agent command directly, no process management.
|
||||
pub fn run(app: &App, target: &str, extra: &[OsString]) -> Result<i32> {
|
||||
pub fn run(app: &App, target: &str, model: Option<&str>, extra: &[OsString]) -> Result<i32> {
|
||||
let agent = require_agent(app, target)?;
|
||||
let extra_args: Vec<String> = extra.iter().map(|o| o.to_string_lossy().to_string()).collect();
|
||||
let exec = resolve_exec(app, agent, &extra_args, &BTreeMap::new())?;
|
||||
let mut extra_args: Vec<String> = extra
|
||||
.iter()
|
||||
.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)?;
|
||||
extra_args.extend(a);
|
||||
extra_env.extend(e);
|
||||
}
|
||||
let exec = resolve_exec(app, agent, &extra_args, &extra_env)?;
|
||||
if app.dry_run() {
|
||||
app.log.dry(format!(
|
||||
"would run {} {}",
|
||||
@@ -339,6 +358,52 @@ fn cwd_string() -> Option<String> {
|
||||
std::env::current_dir().ok().map(|p| p.display().to_string())
|
||||
}
|
||||
|
||||
/// Resolve --model against the local runtimes and build the transmission
|
||||
/// channel for the agent (issue #71): the agent's `model_env` variable wins,
|
||||
/// then its `model_arg` flag, then the generic AM_MODEL variable. An unknown
|
||||
/// model is an explicit error listing the available runtimes.
|
||||
pub fn apply_model(
|
||||
app: &App,
|
||||
agent: &AgentDef,
|
||||
model: &str,
|
||||
) -> Result<(Vec<String>, BTreeMap<String, String>)> {
|
||||
let inventory = crate::models::detect(app);
|
||||
if inventory.find(model).is_none() {
|
||||
let known: Vec<String> = inventory.models.iter().map(|m| m.name.clone()).collect();
|
||||
let hint = if known.is_empty() {
|
||||
"no model runtime detected — run 'am models' to check your ollama, llama.cpp or LM Studio setup".to_string()
|
||||
} else {
|
||||
format!("available models: {} — run 'am models' for details", known.join(", "))
|
||||
};
|
||||
bail!("model '{model}' not found on any local runtime ({hint})");
|
||||
}
|
||||
let mut args = Vec::new();
|
||||
let mut env = BTreeMap::new();
|
||||
if let Some(var) = &agent.model_env {
|
||||
env.insert(var.clone(), model.to_string());
|
||||
app.log.verbose(&format!(
|
||||
"model '{model}' transmitted to {} via {var}",
|
||||
agent.name
|
||||
));
|
||||
} else if let Some(flag) = &agent.model_arg {
|
||||
args.push(flag.clone());
|
||||
args.push(model.to_string());
|
||||
app.log.verbose(&format!(
|
||||
"model '{model}' transmitted to {} via {flag}",
|
||||
agent.name
|
||||
));
|
||||
} else {
|
||||
// Generic fallback: many local agents read AM_MODEL; the config can
|
||||
// pin a specific channel (model_env / model_arg) instead.
|
||||
env.insert("AM_MODEL".to_string(), model.to_string());
|
||||
app.log.info(&format!(
|
||||
"agent '{}' declares no model_env/model_arg — exported AM_MODEL={model}; set model_env or model_arg in the config to use a specific channel",
|
||||
agent.name
|
||||
));
|
||||
}
|
||||
Ok((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()?;
|
||||
|
||||
Reference in New Issue
Block a user