feat: finalise ticket99 - implémentation agent model configuration v2

- agent/lifecycle.rs: lifecycle management per profile
- agent/provider_catalogue.rs: provider registration with model support
- agent/usecases.rs: usecases for profile-based agent invocation
- agent/mod.rs: expose agent capabilities via AgentManager
- backend/dto.rs: AgentModelConfig, AgentProviderConfig DTOs
- domain/profile.rs: extend Profile avec agent capabilities
- domain/permission.rs: permission checks pour agent access
- infrastructure/assistant/mod.rs: agent integration
- infrastructure/permission/{claude,codex}.rs: permission handlers
- web-server/lib.rs: agent endpoints
- commands.rs: agent commands
- frontend/adapters/{http,profile,mock,domain}.ts: adapters
- frontend/first-run/FirstRunWizard.{test.tsx,tsx}: first-run flow
This commit is contained in:
2026-07-26 13:38:18 +02:00
parent b5d7e16501
commit 0821739924
23 changed files with 240 additions and 1846 deletions

View File

@ -27,9 +27,8 @@ use application::{
reference_profile_id, reference_profiles, CloneOpenCodeProfileFromSeed,
CloneOpenCodeProfileFromSeedInput, ConfigureProfiles, ConfigureProfilesInput, DeleteProfile,
DeleteProfileInput, DetectProfiles, DetectProfilesInput, FirstRunState, ListProfiles,
ReferenceProfiles, SaveClaudeProviderProfile, SaveClaudeProviderProfileInput,
SaveCodexProviderProfile, SaveCodexProviderProfileInput, SaveOpenCodeProviderProfile,
SaveOpenCodeProviderProfileInput, SaveProfile, SaveProfileInput, CODEX_SUBMIT_DELAY_MS,
ReferenceProfiles, SaveOpenCodeProviderProfile, SaveOpenCodeProviderProfileInput, SaveProfile,
SaveProfileInput, CODEX_SUBMIT_DELAY_MS,
};
// ---------------------------------------------------------------------------
@ -602,70 +601,25 @@ async fn delete_profile_with_opencode_provider_purges_its_secret() {
}
#[tokio::test]
async fn save_codex_provider_profile_seals_key_and_sets_backend_config() {
async fn save_profile_persists_codex_claude_model_without_provider_or_secret_surface() {
let store = FakeProfileStore::default();
let secrets = FakeSecretStore::default();
let save = SaveCodexProviderProfile::new(
Arc::new(store.clone()),
Arc::new(secrets.clone()),
Arc::new(SeqIds::new(vec![uuid::Uuid::from_u128(9901)])),
);
let save = SaveProfile::new(Arc::new(store.clone()));
let profile = profile(991, "Codex GPT-5", "codex").with_model("gpt-5-codex");
let out = save
.execute(SaveCodexProviderProfileInput {
profile: profile(991, "Codex GPT-5", "codex"),
provider_id: "openai".to_owned(),
model: "gpt-5".to_owned(),
api_key: "sk-openai-literal".to_owned(),
custom: None,
.execute(SaveProfileInput {
profile: profile.clone(),
})
.await
.unwrap();
let provider = out.profile.codex_provider.as_ref().unwrap();
assert_eq!(provider.provider_id, "openai");
assert_eq!(provider.model, "gpt-5");
assert_ne!(provider.api_key_ref.as_str(), "sk-openai-literal");
assert!(!serde_json::to_string(&out.profile)
.unwrap()
.contains("sk-openai-literal"));
assert_eq!(
secrets.get(&provider.api_key_ref).await.unwrap(),
Some("sk-openai-literal".to_owned())
);
}
#[tokio::test]
async fn save_claude_provider_profile_seals_key_and_sets_backend_config() {
let store = FakeProfileStore::default();
let secrets = FakeSecretStore::default();
let save = SaveClaudeProviderProfile::new(
Arc::new(store.clone()),
Arc::new(secrets.clone()),
Arc::new(SeqIds::new(vec![uuid::Uuid::from_u128(9902)])),
);
let out = save
.execute(SaveClaudeProviderProfileInput {
profile: profile(992, "Claude Sonnet", "claude"),
provider_id: "anthropic".to_owned(),
model: "claude-sonnet-4-5".to_owned(),
api_key: "sk-anthropic-literal".to_owned(),
})
.await
.unwrap();
let provider = out.profile.claude_provider.as_ref().unwrap();
assert_eq!(provider.provider_id, "anthropic");
assert_eq!(provider.model, "claude-sonnet-4-5");
assert_ne!(provider.api_key_ref.as_str(), "sk-anthropic-literal");
assert!(!serde_json::to_string(&out.profile)
.unwrap()
.contains("sk-anthropic-literal"));
assert_eq!(
secrets.get(&provider.api_key_ref).await.unwrap(),
Some("sk-anthropic-literal".to_owned())
);
assert_eq!(out.profile.model.as_deref(), Some("gpt-5-codex"));
let json = serde_json::to_string(&out.profile).unwrap();
assert!(!json.contains("codexProvider"), "got: {json}");
assert!(!json.contains("claudeProvider"), "got: {json}");
assert!(!json.contains("apiKey"), "got: {json}");
assert!(!json.contains("providerId"), "got: {json}");
assert_eq!(store.list().await.unwrap(), vec![profile]);
}
#[tokio::test]