fix: am setup — ecrire settings.providers.<nom>.base_url + default_model (top-level 'providers:' rejete par le validateur)
Le wizard persistait le modele par defaut sous la cle 'providers.<nom>.default_model' -> bloc 'providers:' au top-level de la config user -> 'unknown field providers' (le registre vit sous settings.providers ; le schema exige base_url quand un bloc provider est ecrit). Regression couverte par setup_persists_under_settings_providers. v1.1.1
This commit is contained in:
Generated
+1
-1
@@ -21,7 +21,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agent-manager"
|
name = "agent-manager"
|
||||||
version = "1.1.0"
|
version = "1.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "agent-manager"
|
name = "agent-manager"
|
||||||
version = "1.1.0"
|
version = "1.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Manage local AI coding agents: list, install, start, stop, update — with automatic dependency handling and a YAML-driven catalog."
|
description = "Manage local AI coding agents: list, install, start, stop, update — with automatic dependency handling and a YAML-driven catalog."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -62,6 +62,14 @@
|
|||||||
> 6 rôles copilot générés pour aichat (am-copilot, am-operator, am-dev,
|
> 6 rôles copilot générés pour aichat (am-copilot, am-operator, am-dev,
|
||||||
> am-do, am-analyst, am-orchestrator), `am ai --role <nom>`, tip
|
> am-do, am-analyst, am-orchestrator), `am ai --role <nom>`, tip
|
||||||
> d'onboarding dans le banner REPL et les commandes IA (non-bloquant).
|
> d'onboarding dans le banner REPL et les commandes IA (non-bloquant).
|
||||||
|
>
|
||||||
|
> 🔧 **Maintenance v1.1.1 (2026-08-20)** : fix `am setup` — le wizard écrivait
|
||||||
|
> `providers.<nom>` au TOP-LEVEL de la config user (clé rejetée par le
|
||||||
|
> validateur : « unknown field `providers` »). Le registre vivant sous
|
||||||
|
> `settings.providers`, le wizard écrit désormais
|
||||||
|
> `settings.providers.<nom>.base_url` + `.default_model` (le schéma exige
|
||||||
|
> base_url quand un bloc provider est écrit). Test de régression
|
||||||
|
> `setup_persists_under_settings_providers`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+57
-4
@@ -229,10 +229,26 @@ pub fn run(app: &App) -> Result<i32> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Default model.
|
// 3) Default model. The registry lives under settings.providers — a
|
||||||
|
// top-level `providers:` key is rejected by the schema validator, and a
|
||||||
|
// provider block written to the user config requires base_url too.
|
||||||
let model = pick_model(app, &provider)?;
|
let model = pick_model(app, &provider)?;
|
||||||
if provider != "custom" {
|
if provider == "ollama" {
|
||||||
config_cmd::persist_setting(app, &format!("providers.{provider}.default_model"), &model)?;
|
// base_url was already written by the Add above.
|
||||||
|
config_cmd::persist_setting(app, "settings.providers.ollama.default_model", &model)?;
|
||||||
|
} else if provider != "custom" {
|
||||||
|
if let Some(def) = crate::providers::get(&app.config, &provider) {
|
||||||
|
config_cmd::persist_setting(
|
||||||
|
app,
|
||||||
|
&format!("settings.providers.{provider}.base_url"),
|
||||||
|
&def.base_url,
|
||||||
|
)?;
|
||||||
|
config_cmd::persist_setting(
|
||||||
|
app,
|
||||||
|
&format!("settings.providers.{provider}.default_model"),
|
||||||
|
&model,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) Default provider.
|
// 4) Default provider.
|
||||||
@@ -295,11 +311,16 @@ mod tests {
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
static COUNTER: AtomicU32 = AtomicU32::new(0);
|
static COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
fn app_with(settings_yaml: &str) -> App {
|
fn app_with(settings_yaml: &str) -> App {
|
||||||
|
app_with_path(settings_yaml).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_with_path(settings_yaml: &str) -> (App, PathBuf) {
|
||||||
let id = COUNTER.fetch_add(1, Ordering::SeqCst);
|
let id = COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||||
let dir = std::env::temp_dir().join(format!(
|
let dir = std::env::temp_dir().join(format!(
|
||||||
"am-setup-test-{}-{id}",
|
"am-setup-test-{}-{id}",
|
||||||
@@ -315,7 +336,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let cli = Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
|
let cli = Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
|
||||||
App::from_cli(cli).unwrap()
|
(App::from_cli(cli).unwrap(), cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -333,4 +354,36 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(!needs_setup(&app));
|
assert!(!needs_setup(&app));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn setup_persists_under_settings_providers() {
|
||||||
|
// Regression: a top-level `providers:` key is rejected by the
|
||||||
|
// schema validator ("unknown field `providers`"). The wizard must
|
||||||
|
// write settings.providers.<name>.default_model.
|
||||||
|
let (app, cfg) = app_with_path("");
|
||||||
|
config_cmd::persist_setting(
|
||||||
|
&app,
|
||||||
|
"settings.providers.deepseek.base_url",
|
||||||
|
"https://api.deepseek.com",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
config_cmd::persist_setting(
|
||||||
|
&app,
|
||||||
|
"settings.providers.deepseek.default_model",
|
||||||
|
"deepseek-reasoner",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
config_cmd::persist_setting(&app, "settings.default_provider", "deepseek").unwrap();
|
||||||
|
|
||||||
|
// Reloading the config must succeed (validation passes) and the
|
||||||
|
// merged registry must reflect the new default model.
|
||||||
|
let cli = Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
|
||||||
|
let app2 = App::from_cli(cli).expect("config must stay valid after setup writes");
|
||||||
|
let def = crate::providers::get(&app2.config, "deepseek").unwrap();
|
||||||
|
assert_eq!(def.default_model.as_deref(), Some("deepseek-reasoner"));
|
||||||
|
assert_eq!(
|
||||||
|
crate::providers::default_name(&app2.config).as_deref(),
|
||||||
|
Some("deepseek")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user