feat: catalogue dynamique modèles Codex/Claude avec compatibilité CLI locale

Ajout du catalogue enrichi pour les modèles Codex et Claude avec:
- Compatibilité estimée avec la version CLI locale détectée
- Source d'origine (catalogue/Provider) pour chaque entrée
- Support du catalogue Provider API externe
- Matrice de compatibilité embarquée dans l'application

Frontend:
- UI de configuration des modèles avec affichage des états de compatibilité
- Suggestions dynamiques avec badges de compatibilité
- Messages d'aide contextuels (compatible/unknown/likelyTooRecent)
- Alertes non-bloquantes pour les modèles trop récents
- Gestion des échecs de catalogue avec saisie manuelle conservée

Backend:
- Ports CliVersionReader, ProviderModelCatalogue, CompatibilityMatrixSource
- Implémentations: ProcessCliVersionReader, HttpProviderModelCatalogue, EmbeddedCompatibilityMatrix
- Enrichissement des DTOs avec compatibility, cli_version, warnings
- Tests unitaires complets pour le resolver de catalogue
This commit is contained in:
2026-07-26 16:09:10 +02:00
parent e7bf1d3666
commit ca70ec75f4
25 changed files with 1582 additions and 167 deletions

View File

@ -1189,20 +1189,20 @@ pub async fn list_opencode_providers(
Ok(state.list_opencode_providers.execute().into())
}
/// `list_claude_models` — static curated Claude model catalogue.
/// `list_claude_models` — enriched Claude model catalogue.
#[tauri::command]
pub async fn list_claude_models(
state: State<'_, AppState>,
) -> Result<ProfileModelCatalogDto, ErrorDto> {
Ok(state.list_claude_models.execute().into())
Ok(state.list_claude_models.execute().await.into())
}
/// `list_codex_models` — static curated Codex model catalogue.
/// `list_codex_models` — enriched Codex model catalogue.
#[tauri::command]
pub async fn list_codex_models(
state: State<'_, AppState>,
) -> Result<ProfileModelCatalogDto, ErrorDto> {
Ok(state.list_codex_models.execute().into())
Ok(state.list_codex_models.execute().await.into())
}
/// `save_opencode_provider_profile` — create or replace an OpenCode profile

View File

@ -141,21 +141,30 @@ fn clone_profile_from_seed_request_deserialises_camelcase_overrides() {
#[test]
fn profile_model_catalogue_dto_serialises_searchable_camelcase_entries() {
let dto = ProfileModelCatalogDto(vec![app_tauri_lib::dto::ProfileModelCatalogEntryDto {
adapter: StructuredAdapter::Codex,
model_id: "gpt-5-codex".to_owned(),
display_name: "GPT-5 Codex".to_owned(),
aliases: vec!["codex".to_owned()],
recommended: true,
}]);
let dto = ProfileModelCatalogDto {
models: vec![app_tauri_lib::dto::ProfileModelCatalogEntryDto {
adapter: StructuredAdapter::Codex,
model_id: "gpt-5-codex".to_owned(),
display_name: "GPT-5 Codex".to_owned(),
aliases: vec!["codex".to_owned()],
recommended: true,
compatibility: domain::ModelCompatibility::Compatible,
source: domain::ModelCatalogSource::Catalogue,
}],
cli_version: Some("0.45.1".to_owned()),
warnings: vec!["provider unavailable".to_owned()],
};
let value = serde_json::to_value(&dto).unwrap();
let arr = value.as_array().expect("transparent array");
assert_eq!(arr[0]["adapter"], "codex");
assert_eq!(arr[0]["modelId"], "gpt-5-codex");
assert_eq!(arr[0]["displayName"], "GPT-5 Codex");
assert_eq!(arr[0]["aliases"], json!(["codex"]));
assert_eq!(arr[0]["recommended"], true);
assert_eq!(value["cliVersion"], "0.45.1");
assert_eq!(value["warnings"], json!(["provider unavailable"]));
assert_eq!(value["models"][0]["adapter"], "codex");
assert_eq!(value["models"][0]["modelId"], "gpt-5-codex");
assert_eq!(value["models"][0]["displayName"], "GPT-5 Codex");
assert_eq!(value["models"][0]["aliases"], json!(["codex"]));
assert_eq!(value["models"][0]["recommended"], true);
assert_eq!(value["models"][0]["compatibility"], "compatible");
assert_eq!(value["models"][0]["source"], "catalogue");
}
#[test]