feat(backend): catalogue de providers OpenCode dynamique + provider personnalisé (#92)

Le catalogue de providers OpenCode lit désormais le cache local
~/.cache/opencode/models.json pour refléter les providers réellement
disponibles, avec repli garanti sur le catalogue statique en cas
d'absence ou d'erreur de lecture du cache. Ajout d'un champ additif
`custom` sur OpenCodeProviderConfig pour permettre à l'utilisateur de
déclarer un provider hors catalogue (id + clé API en saisie libre).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 12:43:59 +02:00
parent 162e3ae641
commit e943a0efed
10 changed files with 502 additions and 57 deletions

View File

@ -15,7 +15,9 @@ use std::sync::Arc;
use domain::ids::ProfileId;
use domain::ports::{AgentRuntime, IdGenerator, ProfileStore, SecretRef, SecretStore};
use domain::profile::{AgentProfile, OpenCodeConfig, OpenCodeProviderConfig, StructuredAdapter};
use domain::profile::{
AgentProfile, CustomProviderConfig, OpenCodeConfig, OpenCodeProviderConfig, StructuredAdapter,
};
use crate::error::AppError;
@ -293,6 +295,9 @@ pub struct SaveOpenCodeProviderProfileInput {
/// re-sealed under the profile's existing `SecretRef` on edit — never
/// persisted as a literal in `profiles.json`.
pub api_key: String,
/// Optional custom-provider configuration (endpoint outside the OpenCode
/// registry). `None` = known provider (unchanged behaviour).
pub custom: Option<CustomProviderConfig>,
}
/// Output of [`SaveOpenCodeProviderProfile::execute`].
@ -349,9 +354,12 @@ impl SaveOpenCodeProviderProfile {
self.secret_store.put(&secret_ref, &input.api_key).await?;
let provider =
let mut provider =
OpenCodeProviderConfig::new(input.provider_id, input.model, secret_ref)
.map_err(|e| AppError::Invalid(e.to_string()))?;
if let Some(custom) = input.custom {
provider = provider.with_custom(custom);
}
let mut profile = input.profile;
profile.opencode_provider = Some(provider);